diff options
Diffstat (limited to 'lib/diameter')
73 files changed, 6539 insertions, 2178 deletions
diff --git a/lib/diameter/bin/diameterc b/lib/diameter/bin/diameterc index c0e83ea1a4..a72ba2d75c 100755 --- a/lib/diameter/bin/diameterc +++ b/lib/diameter/bin/diameterc @@ -73,24 +73,30 @@ gen(Args) -> end. compile(#argv{file = File, options = Opts} = A) -> - try - Spec = diameter_spec_util:parse(File, Opts), - maybe_output(A, Spec, Opts, spec), %% the spec file - maybe_output(A, Spec, Opts, erl), %% the erl file - maybe_output(A, Spec, Opts, hrl), %% The hrl file - 0 + try diameter_dict_util:parse({path, File}, Opts) of + {ok, Spec} -> + maybe_output(A, Spec, Opts, spec), %% the spec file + maybe_output(A, Spec, Opts, erl), %% the erl file + maybe_output(A, Spec, Opts, hrl), %% The hrl file + 0; + {error, Reason} -> + error_msg(diameter_dict_util:format_error(Reason), []), + 1 catch error: Reason -> - error_msg({"ERROR: ~p~n ~p", [Reason, erlang:get_stacktrace()]}), + error_msg("ERROR: ~p~n ~p", [Reason, erlang:get_stacktrace()]), 2 end. maybe_output(#argv{file = File, output = Output}, Spec, Opts, Mode) -> lists:member(Mode, Output) - andalso diameter_codegen:from_spec(File, Spec, Opts, Mode). + andalso diameter_codegen:from_dict(File, Spec, Opts, Mode). error_msg({Fmt, Args}) -> - io:format(standard_error, Fmt ++ "~n", Args). + error_msg(Fmt, Args). + +error_msg(Fmt, Args) -> + io:format(standard_error, "** " ++ Fmt ++ "~n", Args). norm({_,_} = T) -> T; diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 2d8edb1301..93e2603c10 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -107,7 +107,9 @@ belonging to the application.</p> <marker id="application_module"/> </item> -<tag><c>application_module() = Mod | [Mod | ExtraArgs]</c></tag> +<tag><c>application_module() = Mod + | [Mod | ExtraArgs] + | #diameter_callback{}</c></tag> <item> <code> Mod = atom() @@ -125,6 +127,14 @@ specified to <seealso marker="#call">call/4</seealso>, in which case the call-specific arguments are appended to any specified with the callback module.</p> +<p> +Specifying a <c>#diameter_callback{}</c> record allows individual +functions to be configured in place of the usual <seealso +marker="diameter_app">diameter_app(3)</seealso> callbacks, with +default implementations provided by module <c>diameter_callback</c> +unless otherwise specified. +See that module for details.</p> + <marker id="application_opt"/> </item> diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index e7c530f1b8..cc638dbc18 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -36,7 +36,7 @@ under the License. <!-- ===================================================================== --> <file>diameter_dict</file> -<filesummary>Dictionary inteface of the diameter application.</filesummary> +<filesummary>Dictionary interface of the diameter application.</filesummary> <description> <p> @@ -44,9 +44,9 @@ A diameter service as configured with <seealso marker="diameter#start_service">diameter:start_service/2</seealso> specifies one or more supported Diameter applications. Each Diameter application specifies a dictionary module that knows how -to encode and decode its messages and AVP's. +to encode and decode its messages and AVPs. The dictionary module is in turn generated from a file that defines -these messages and AVP's. +these messages and AVPs. The format of such a file is defined in <seealso marker="#FILE_FORMAT">FILE FORMAT</seealso> below. Users add support for their specific applications by creating @@ -56,7 +56,7 @@ resulting dictionaries modules on a service.</p> <p> The codec generation also results in a hrl file that defines records -for the messages and grouped AVP's defined for the application, these +for the messages and grouped AVPs defined for the application, these records being what a user of the diameter application sends and receives. (Modulo other available formats as discussed in <seealso marker="diameter_app">diameter_app(3)</seealso>.) @@ -74,14 +74,14 @@ corresponding to applications defined in section 2.4 of RFC 3588: application with application identifier 0, <c>diameter_gen_accounting</c> for the Diameter Base Accounting application with application identifier 3 and -<c>diameter_gen_relay</c>the Relay application with application +<c>diameter_gen_relay</c> the Relay application with application identifier 0xFFFFFFFF. The Common Message and Relay applications are the only applications that diameter itself has any specific knowledge of. The Common Message application is used for messages that diameter itself handles: CER/CEA, DWR/DWA and DPR/DPA. The Relay application is given special treatment with regard to -encode/decode since the messages and AVP's it handles are not specifically +encode/decode since the messages and AVPs it handles are not specifically defined.</p> <marker id="FILE_FORMAT"/> @@ -94,18 +94,16 @@ defined.</p> <p> A dictionary file consists of distinct sections. -Each section starts with a line consisting of a tag -followed by zero or more arguments. -Each section ends at the the start of the next section or end of file. +Each section starts with a tag followed by zero or more arguments +and ends at the the start of the next section or end of file. Tags consist of an ampersand character followed by a keyword and are separated from their arguments by whitespace. -Whitespace within a section separates individual tokens but its -quantity is insignificant.</p> +Whitespace separates individual tokens but is otherwise insignificant.</p> <p> The tags, their arguments and the contents of each corresponding section are as follows. -Each section can occur at most once unless otherwise specified. +Each section can occur multiple times unless otherwise specified. The order in which sections are specified is unimportant.</p> <taglist> @@ -115,7 +113,8 @@ The order in which sections are specified is unimportant.</p> <p> Defines the integer Number as the Diameter Application Id of the application in question. -Required if the dictionary defines <c>@messages</c>. +Can occur at most once and is required if the dictionary defines +<c>@messages</c>. The section has empty content.</p> <p> @@ -136,16 +135,13 @@ Example:</p> <item> <p> Defines the name of the generated dictionary module. -The section has empty content. -Mod must match the regular expression '^[a-zA-Z0-9][-_a-zA-Z0-9]*$'; -that is, contains only alphanumerics, hyphens and underscores begin with an -alphanumeric.</p> +Can occur at most once and defaults to the name of the dictionary file +minus any extension if unspecified. +The section has empty content.</p> <p> -A name is optional and defaults to the name of the dictionary file -minus any extension. -Note that a generated module must have a unique name an not colide -with another module in the system.</p> +Note that a dictionary module should have a unique name so as not collide +with existing modules in the system.</p> <p> Example:</p> @@ -159,22 +155,22 @@ Example:</p> <tag><c>@prefix Name</c></tag> <item> <p> -Defines Name as the prefix to be added to record and constant names in -the generated dictionary module and hrl. -The section has empty content. -Name must be of the same form as a @name.</p> +Defines Name as the prefix to be added to record and constant names +(followed by a <c>'_'</c> character) in the generated dictionary +module and hrl. +Can occur at most once. +The section has empty content.</p> <p> -A prefix is optional but can -be used to disambiguate record and constant names -resulting from similarly named messages and AVP's in different -Diameter applications.</p> +A prefix is optional but can be be used to disambiguate between record +and constant names resulting from similarly named messages and AVPs in +different Diameter applications.</p> <p> Example:</p> <code> -@prefix etsi_e2_ +@prefix etsi_e2 </code> </item> @@ -182,10 +178,12 @@ Example:</p> <tag><c>@vendor Number Name</c></tag> <item> <p> -Defines the integer Number as the the default Vendor-ID of AVP's for +Defines the integer Number as the the default Vendor-Id of AVPs for which the V flag is set. Name documents the owner of the application but is otherwise unused. +Can occur at most once and is required if an AVP sets the V flag and +is not otherwise assigned a Vendor-Id. The section has empty content.</p> <p> @@ -200,10 +198,9 @@ Example:</p> <tag><c>@avp_vendor_id Number</c></tag> <item> <p> -Defines the integer Number as the Vendor-ID of the AVP's listed in the +Defines the integer Number as the Vendor-Id of the AVPs listed in the section content, overriding the <c>@vendor</c> default. -The section content consists of AVP names. -Can occur zero or more times (with different values of Number).</p> +The section content consists of AVP names.</p> <p> Example:</p> @@ -221,13 +218,27 @@ Region-Set <tag><c>@inherits Mod</c></tag> <item> <p> -Defines the name of a generated dictionary module containing AVP -definitions referenced by the dictionary but not defined by it. -The section content is empty.</p> +Defines the name of a dictionary module containing AVP +definitions that should be imported into the current dictionary. +The section content consists of the names of those AVPs whose +definitions should be imported from the dictionary, an empty list +causing all to be imported. +Any listed AVPs must not be defined in the current dictionary and +it is an error to inherit the same AVP from more than one +dictionary.</p> <p> -Can occur 0 or more times (with different values of Mod) but all -dictionaries should typically inherit RFC3588 AVPs from +Note that an inherited AVP that sets the V flag takes its Vendor-Id +from either <c>@avp_vendor_id</c> in the inheriting dictionary or +<c>@vendor</c> in the inherited dictionary. +In particular, <c>@avp_vendor_id</c> in the inherited dictionary is +ignored. +Inheriting from a dictionary that specifies the required <c>@vendor</c> +is equivalent to using <c>@avp_vendor_id</c> with a copy of the +dictionary's definitions but the former makes for easier reuse.</p> + +<p> +All dictionaries should typically inherit RFC3588 AVPs from <c>diameter_gen_base_rfc3588</c>.</p> <p> @@ -248,13 +259,11 @@ The section consists of definitions of the form</p> <p><c>Name Code Type Flags</c></p> <p> -where Code is the integer AVP code, Flags is a string of V, -M and P characters indicating the flags to be -set on an outgoing AVP or a single - (minus) character if none are to -be set. -Type identifies either an AVP Data Format as defined in <seealso -marker="#DATA_TYPES">DATA TYPES</seealso> below or a -type as defined by a <c>@custom_types</c> tag.</p> +where Code is the integer AVP code, Type identifies an AVP Data Format +as defined in <seealso marker="#DATA_TYPES">DATA TYPES</seealso> below, +and Flags is a string of V, M and P characters indicating the flags to be +set on an outgoing AVP or a single <c>'-'</c> (minus) character if +none are to be set.</p> <p> Example:</p> @@ -262,8 +271,8 @@ Example:</p> <code> @avp_types -Location-Information 350 Grouped VM -Requested-Information 353 Enumerated V +Location-Information 350 Grouped MV +Requested-Information 353 Enumerated V </code> <p> @@ -276,21 +285,36 @@ to 0 as mandated by the current draft standard.</p> <tag><c>@custom_types Mod</c></tag> <item> <p> -Defines AVPs for which module Mod provides encode/decode. -The section contents consists of type names. -For each AVP Name defined with custom type Type, Mod should export the -function Name/3 with arguments encode|decode, Type and Data, -the latter being the term to be encoded/decoded. -The function returns the encoded/decoded value.</p> +Specifies AVPs for which module Mod provides encode/decode functions. +The section contents consists of AVP names. +For each such name, <c>Mod:Name(encode|decode, Type, Data)</c> is +expected to provide encode/decode for values of the AVP, where Name is +the name of the AVP, Type is it's type as declared in the +<c>@avp_types</c> section of the dictionary and Data is the value to +encode/decode.</p> + +<p> +Example:</p> + +<code> +@custom_types rfc4005_avps + +Framed-IP-Address +</code> +</item> +<tag><c>@codecs Mod</c></tag> +<item> <p> -Can occur 0 or more times (with different values of Mod).</p> +Like <c>@custom_types</c> but requires the specified module to export +<c>Mod:Type(encode|decode, Name, Data)</c> rather than +<c>Mod:Name(encode|decode, Type, Data)</c>.</p> <p> Example:</p> <code> -@custom_types rfc4005_types +@codecs rfc4005_avps Framed-IP-Address </code> @@ -360,6 +384,10 @@ SIP-Deregistration-Reason ::= < AVP Header: 383 > [ SIP-Reason-Info ] * [ AVP ] </code> + +<p> +Specifying a Vendor-Id in the definition of a grouped AVP is +equivalent to specifying it with <c>@avp_vendor_id</c>.</p> </item> <tag><c>@enum Name</c></tag> @@ -371,11 +399,9 @@ Integer values can be prefixed with 0x to be interpreted as hexidecimal.</p> <p> -Can occur 0 or more times (with different values of Name). -The AVP in question can be defined in an inherited dictionary in order -to introduce additional values. -An AVP so extended must be referenced by in a <c>@messages</c> or -<c>@grouped</c> section.</p> +Note that the AVP in question can be defined in an inherited +dictionary in order to introduce additional values to an enumeration +otherwise defined in another dictionary.</p> <p> Example:</p> @@ -390,11 +416,18 @@ REMOVE_SIP_SERVER 3 </code> </item> +<tag><c>@end</c></tag> +<item> +<p> +Causes parsing of the dictionary to terminate: +any remaining content is ignored.</p> +</item> + </taglist> <p> Comments can be included in a dictionary file using semicolon: -text from a semicolon to end of line is ignored.</p> +characters from a semicolon to end of line are ignored.</p> <marker id="MESSAGE_RECORDS"/> </section> diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index e2723f3e99..6e364a3200 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -36,6 +36,120 @@ first.</p> <!-- ===================================================================== --> +<section><title>Diameter 1.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix faulty cleanup after diameter:remove_transport/2.</p> + <p> + Removing a transport removed the configuration but did + not prevent the transport process from being restarted.</p> + <p> + Own Id: OTP-9756</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add support for TLS over TCP.</p> + <p> + RFC 3588 requires that a Diameter server support TLS. In + practice this seems to mean TLS over SCTP since there are + limitations with running over SCTP: see RFC 6083 (DTLS + over SCTP), which is a response to RFC 3436 (TLS over + SCTP). The current RFC 3588 draft acknowledges this by + equating TLS with TLS/TCP and DTLS/SCTP.</p> + <p> + TLS handshaking can take place either following a CER/CEA + that negotiates TLS using the Inband-Security-Id AVP (the + method documented in RFC 3588) or immediately following + connection establishment (the method added to the current + draft).</p> + <p> + Own Id: OTP-9605</p> + </item> + <item> + <p> + Improvements to the dictionary parser.</p> + <p> + The dictionary parser now emits useful error messages in + case of faults in the input file, also identifying the + line number at which the fault was detected. There are + semantic checks that were missing in the previous parser, + a fault in the interpretation of vendor id's in + combination with @inherits has been fixed and @end can be + used to terminate parsing explicitly instead of always + parsing to end of file.</p> + <p> + Own Id: OTP-9639</p> + </item> + <item> + <p> + Improve dictionary reusability.</p> + <p> + Reusing a dictionary just to get a different generated + module name or prefix previously required taking a copy + of the source, which may consist of several files if + inheritance is used, just to edit a couple of lines which + don't affect the semantics of the Diameter application + being defined. Options --name, --prefix and --inherits + have been added to diameterc to allow corresponding + values to be set at compile time.</p> + <p> + Own Id: OTP-9641</p> + </item> + <item> + <p> + Add capabilities_cb transport option.</p> + <p> + Its value is a function that's applied to the transport + reference and capabilities record after capabilities + exchange. If a callback returns anything but 'ok' then + the connection is closed. In the case of an incoming CER, + the callback can return a result code with which to + answer. Multiple callbacks can be specified and are + applied until either all return 'ok' or one doesn't.</p> + <p> + This provides a way to reject a peer connection.</p> + <p> + Own Id: OTP-9654</p> + </item> + <item> + <p> + Add @codecs to dictionary format.</p> + <p> + The semantics are similar to @custom_types but results in + codec functions of the form TypeName(encode|decode, + AvpName, Data) rather than AvpName(encode|decode, + TypeName, Data). That is, the role of the AVP name and + Diameter type name are reversed. This eliminates the need + for exporting one function for each AVP sharing a common + specialized encode/decode.</p> + <p> + Own Id: OTP-9708 Aux Id: OTP-9639 </p> + </item> + <item> + <p> + Add #diameter_callback{} for more flexible callback + configuration.</p> + <p> + The record allows individual functions to be configured + for each of the diameter_app(3) callbacks, as well as a + default callback.</p> + <p> + Own Id: OTP-9777</p> + </item> + </list> + </section> + +</section> + <section><title>Diameter 0.10</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/diameter/examples/GNUmakefile b/lib/diameter/examples/code/GNUmakefile index 4c3f87939b..a0669119d2 100644 --- a/lib/diameter/examples/GNUmakefile +++ b/lib/diameter/examples/code/GNUmakefile @@ -1,19 +1,19 @@ -# +# # %CopyrightBegin% -# +# # Copyright Ericsson AB 2010-2011. 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% # diff --git a/lib/diameter/examples/client.erl b/lib/diameter/examples/code/client.erl index 36a77dd524..9e65f98de0 100644 --- a/lib/diameter/examples/client.erl +++ b/lib/diameter/examples/code/client.erl @@ -112,7 +112,7 @@ cast(Name) -> {'Auth-Application-Id', 0}, {'Re-Auth-Request-Type', 1}], diameter:call(Name, ?APP_ALIAS, RAR, [detach]). - + cast() -> cast(?SVC_NAME). diff --git a/lib/diameter/examples/client_cb.erl b/lib/diameter/examples/code/client_cb.erl index 524a8f94a1..524a8f94a1 100644 --- a/lib/diameter/examples/client_cb.erl +++ b/lib/diameter/examples/code/client_cb.erl diff --git a/lib/diameter/examples/peer.erl b/lib/diameter/examples/code/peer.erl index 89203e15c3..89203e15c3 100644 --- a/lib/diameter/examples/peer.erl +++ b/lib/diameter/examples/code/peer.erl diff --git a/lib/diameter/examples/redirect.erl b/lib/diameter/examples/code/redirect.erl index b54701243f..b54701243f 100644 --- a/lib/diameter/examples/redirect.erl +++ b/lib/diameter/examples/code/redirect.erl diff --git a/lib/diameter/examples/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl index ea7ad38749..ea7ad38749 100644 --- a/lib/diameter/examples/redirect_cb.erl +++ b/lib/diameter/examples/code/redirect_cb.erl diff --git a/lib/diameter/examples/relay.erl b/lib/diameter/examples/code/relay.erl index deecb1cfc0..deecb1cfc0 100644 --- a/lib/diameter/examples/relay.erl +++ b/lib/diameter/examples/code/relay.erl diff --git a/lib/diameter/examples/relay_cb.erl b/lib/diameter/examples/code/relay_cb.erl index 9ed6517d5c..9ed6517d5c 100644 --- a/lib/diameter/examples/relay_cb.erl +++ b/lib/diameter/examples/code/relay_cb.erl diff --git a/lib/diameter/examples/sctp.erl b/lib/diameter/examples/code/sctp.erl index 2e0e9d8b0b..08de023571 100644 --- a/lib/diameter/examples/sctp.erl +++ b/lib/diameter/examples/code/sctp.erl @@ -1,3 +1,21 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% -module(sctp). diff --git a/lib/diameter/examples/server.erl b/lib/diameter/examples/code/server.erl index ebb408e501..ebb408e501 100644 --- a/lib/diameter/examples/server.erl +++ b/lib/diameter/examples/code/server.erl diff --git a/lib/diameter/examples/server_cb.erl b/lib/diameter/examples/code/server_cb.erl index 43b8e24b5c..43b8e24b5c 100644 --- a/lib/diameter/examples/server_cb.erl +++ b/lib/diameter/examples/code/server_cb.erl diff --git a/lib/diameter/examples/dict/rfc4004_mip.dia b/lib/diameter/examples/dict/rfc4004_mip.dia new file mode 100644 index 0000000000..575ad4394a --- /dev/null +++ b/lib/diameter/examples/dict/rfc4004_mip.dia @@ -0,0 +1,280 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4004, Diameter Mobile IPv4 Application +;; +;; Edits: +;; +;; - MIP-nonce -> MIP-Nonce +;; - Session-ID -> Session-Id +;; - Omit MIP-HA-to-MN-SPI, MIP-MN-FA-SPI and MIP-MN-HA-SPI, none of +;; which are defined. +;; + +@id 2 + +@inherits rfc3588_base + +;; =========================================================================== + +@avp_types + + MIP-Reg-Request 320 OctetString M + MIP-Reg-Reply 321 OctetString M + MIP-MN-AAA-Auth 322 Grouped M + MIP-Mobile-Node-Address 333 Address M + MIP-Home-Agent-Address 334 Address M + MIP-Candidate-Home-Agent-Host 336 DiamIdent M + MIP-Feature-Vector 337 Unsigned32 M + MIP-Auth-Input-Data-Length 338 Unsigned32 M + MIP-Authenticator-Length 339 Unsigned32 M + MIP-Authenticator-Offset 340 Unsigned32 M + MIP-MN-AAA-SPI 341 Unsigned32 M + MIP-Filter-Rule 342 IPFilterRule M + MIP-FA-Challenge 344 OctetString M + MIP-Originating-Foreign-AAA 347 Grouped M + MIP-Home-Agent-Host 348 Grouped M + + MIP-FA-to-HA-SPI 318 Unsigned32 M + MIP-FA-to-MN-SPI 319 Unsigned32 M + MIP-HA-to-FA-SPI 323 Unsigned32 M + MIP-MN-to-FA-MSA 325 Grouped M + MIP-FA-to-MN-MSA 326 Grouped M + MIP-FA-to-HA-MSA 328 Grouped M + MIP-HA-to-FA-MSA 329 Grouped M + MIP-MN-to-HA-MSA 331 Grouped M + MIP-HA-to-MN-MSA 332 Grouped M + MIP-Nonce 335 OctetString M + MIP-Session-Key 343 OctetString M + MIP-Algorithm-Type 345 Enumerated M + MIP-Replay-Mode 346 Enumerated M + MIP-MSA-Lifetime 367 Unsigned32 M + +;; =========================================================================== + +@messages + + ;; 5.1. AA-Mobile-Node-Request + + AMR ::= < Diameter Header: 260, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { User-Name } + { Destination-Realm } + { Origin-Host } + { Origin-Realm } + { MIP-Reg-Request } + { MIP-MN-AAA-Auth } + [ Acct-Multi-Session-Id ] + [ Destination-Host ] + [ Origin-State-Id ] + [ MIP-Mobile-Node-Address ] + [ MIP-Home-Agent-Address ] + [ MIP-Feature-Vector ] + [ MIP-Originating-Foreign-AAA ] + [ Authorization-Lifetime ] + [ Auth-Session-State ] + [ MIP-FA-Challenge ] + [ MIP-Candidate-Home-Agent-Host ] + [ MIP-Home-Agent-Host ] + [ MIP-HA-to-FA-SPI ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 5.2. AA-Mobile-Node-Answer + + AMA ::= < Diameter Header: 260, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Acct-Multi-Session-Id ] + [ User-Name ] + [ Authorization-Lifetime ] + [ Auth-Session-State ] + [ Error-Message ] + [ Error-Reporting-Host ] + [ Re-Auth-Request-Type ] + [ MIP-Feature-Vector ] + [ MIP-Reg-Reply ] + [ MIP-MN-to-FA-MSA ] + [ MIP-MN-to-HA-MSA ] + [ MIP-FA-to-MN-MSA ] + [ MIP-FA-to-HA-MSA ] + [ MIP-HA-to-MN-MSA ] + [ MIP-MSA-Lifetime ] + [ MIP-Home-Agent-Address ] + [ MIP-Mobile-Node-Address ] + * [ MIP-Filter-Rule ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ AVP ] + + ;; 5.3. Home-Agent-MIP-Request + + HAR ::= < Diameter Header: 262, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Authorization-Lifetime } + { Auth-Session-State } + { MIP-Reg-Request } + { Origin-Host } + { Origin-Realm } + { User-Name } + { Destination-Realm } + { MIP-Feature-Vector } + [ Destination-Host ] + [ MIP-MN-to-HA-MSA ] + [ MIP-MN-to-FA-MSA ] + [ MIP-HA-to-MN-MSA ] + [ MIP-HA-to-FA-MSA ] + [ MIP-MSA-Lifetime ] + [ MIP-Originating-Foreign-AAA ] + [ MIP-Mobile-Node-Address ] + [ MIP-Home-Agent-Address ] + * [ MIP-Filter-Rule ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 5.4. Home-Agent-MIP-Answer + + HAA ::= < Diameter Header: 262, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ Acct-Multi-Session-Id ] + [ User-Name ] + [ Error-Reporting-Host ] + [ Error-Message ] + [ MIP-Reg-Reply ] + [ MIP-Home-Agent-Address ] + [ MIP-Mobile-Node-Address ] + [ MIP-FA-to-HA-SPI ] + [ MIP-FA-to-MN-SPI ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ AVP ] + +;; =========================================================================== + +@grouped + + MIP-MN-AAA-Auth ::= < AVP Header: 322 > + + { MIP-MN-AAA-SPI } + { MIP-Auth-Input-Data-Length } + { MIP-Authenticator-Length } + { MIP-Authenticator-Offset } + * [ AVP ] + + + MIP-Originating-Foreign-AAA ::= < AVP Header: 347 > + + { Origin-Realm } + { Origin-Host } + * [ AVP ] + + MIP-Home-Agent-Host ::= < AVP Header: 348 > + + { Destination-Realm } + { Destination-Host } + * [ AVP ] + + MIP-FA-to-MN-MSA ::= < AVP Header: 326 > + + { MIP-FA-to-MN-SPI } + { MIP-Algorithm-Type } + { MIP-Session-Key } + * [ AVP ] + + MIP-FA-to-HA-MSA ::= < AVP Header: 328 > + + { MIP-FA-to-HA-SPI } + { MIP-Algorithm-Type } + { MIP-Session-Key } + * [ AVP ] + + MIP-HA-to-FA-MSA ::= < AVP Header: 329 > + + { MIP-HA-to-FA-SPI } + { MIP-Algorithm-Type } + { MIP-Session-Key } + * [ AVP ] + + MIP-HA-to-MN-MSA ::= < AVP Header: 332 > + + ; { MIP-HA-to-MN-SPI } + { MIP-Algorithm-Type } + { MIP-Replay-Mode } + { MIP-Session-Key } + * [ AVP ] + + MIP-MN-to-FA-MSA ::= < AVP Header: 325 > + + ; { MIP-MN-FA-SPI } + { MIP-Algorithm-Type } + { MIP-Nonce } + * [ AVP ] + + MIP-MN-to-HA-MSA ::= < AVP Header: 331 > + + ; { MIP-MN-HA-SPI } + { MIP-Algorithm-Type } + { MIP-Replay-Mode } + { MIP-Nonce } + * [ AVP ] + +;; =========================================================================== + +@enum MIP-Algorithm-Type + + HMAC-SHA-1 2 + +@enum MIP-Replay-Mode + + NONE 1 + TIMESTAMPS 2 + NONCES 3 + +;; =========================================================================== + +@define Result-Code + + ;; 6.1. Transient Failures + + MIP_REPLY_FAILURE 4005 + HA_NOT_AVAILABLE 4006 + BAD_KEY 4007 + MIP_FILTER_NOT_SUPPORTED 4008 + + ;; 6.2. Permanent Failures + + NO_FOREIGN_HA_SERVICE 5024 + END_TO_END_MIP_KEY_ENCRYPTION 5025 diff --git a/lib/diameter/examples/dict/rfc4005_nas.dia b/lib/diameter/examples/dict/rfc4005_nas.dia new file mode 100644 index 0000000000..a4b44e38bb --- /dev/null +++ b/lib/diameter/examples/dict/rfc4005_nas.dia @@ -0,0 +1,740 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4005, Diameter Network Access Server Application +;; +;; Edits: +;; +;; - Acounting-Auth-Method -> Accounting-Auth-Method +;; - Connection-Info -> ConnectInfo +;; - Framed-Appletalk-Link -> Framed-AppleTalk-Link +;; - Framed-Appletalk-Network -> Framed-AppleTalk-Network +;; - Framed-Appletalk-Zone -> Framed-AppleTalk-Zone +;; - Qos-Filter-Rule -> QoS-Filter-Rule +;; - Redirect-Host-Usase -> Redirect-Host-Usage +;; - Redirected-Host -> Redirect-Host +;; - Redirected-Host-Usage -> Redirect-Host-Usage +;; - Redirected-Host-Cache-Time -> Redirect-Max-Cache-Time +;; - Redirected-Max-Cache-Time -> Redirect-Max-Cache-Time +;; + +@id 1 + +@inherits rfc3588_base + +;; =========================================================================== + +@avp_types + + ;; 4. NAS Session AVPs + + NAS-Port 5 Unsigned32 M + NAS-Port-Id 87 UTF8String M + NAS-Port-Type 61 Enumerated M + Called-Station-Id 30 UTF8String M + Calling-Station-Id 31 UTF8String M + Connect-Info 77 UTF8String M + Originating-Line-Info 94 OctetString - + Reply-Message 18 UTF8String M + + ;; 5. NAS Authentication AVPs + + User-Password 2 OctetString M + Password-Retry 75 Unsigned32 M + Prompt 76 Enumerated M + CHAP-Auth 402 Grouped M + CHAP-Algorithm 403 Enumerated M + CHAP-Ident 404 OctetString M + CHAP-Response 405 OctetString M + CHAP-Challenge 60 OctetString M + ARAP-Password 70 OctetString M + ARAP-Challenge-Response 84 OctetString M + ARAP-Security 73 Unsigned32 M + ARAP-Security-Data 74 OctetString M + + ;; 6. NAS Authorization AVPs + + Service-Type 6 Enumerated M + Callback-Number 19 UTF8String M + Callback-Id 20 UTF8String M + Idle-Timeout 28 Unsigned32 M + Port-Limit 62 Unsigned32 M + NAS-Filter-Rule 400 IPFilterRule M + Filter-Id 11 UTF8String M + Configuration-Token 78 OctetString M + QoS-Filter-Rule 407 QoSFilterRule - + Framed-Protocol 7 Enumerated M + Framed-Routing 10 Enumerated M + Framed-MTU 12 Unsigned32 M + Framed-Compression 13 Enumerated M + Framed-IP-Address 8 OctetString M + Framed-IP-Netmask 9 OctetString M + Framed-Route 22 UTF8String M + Framed-Pool 88 OctetString M + Framed-Interface-Id 96 Unsigned64 M + Framed-IPv6-Prefix 97 OctetString M + Framed-IPv6-Route 99 UTF8String M + Framed-IPv6-Pool 100 OctetString M + Framed-IPX-Network 23 UTF8String M + Framed-AppleTalk-Link 37 Unsigned32 M + Framed-AppleTalk-Network 38 Unsigned32 M + Framed-AppleTalk-Zone 39 OctetString M + ARAP-Features 71 OctetString M + ARAP-Zone-Access 72 Enumerated M + Login-IP-Host 14 OctetString M + Login-IPv6-Host 98 OctetString M + Login-Service 15 Enumerated M + Login-TCP-Port 16 Unsigned32 M + Login-LAT-Service 34 OctetString M + Login-LAT-Node 35 OctetString M + Login-LAT-Group 36 OctetString M + Login-LAT-Port 63 OctetString M + + ;; 7. NAS Tunneling + + Tunneling 401 Grouped M + Tunnel-Type 64 Enumerated M + Tunnel-Medium-Type 65 Enumerated M + Tunnel-Client-Endpoint 66 UTF8String M + Tunnel-Server-Endpoint 67 UTF8String M + Tunnel-Password 69 OctetString M + Tunnel-Private-Group-Id 81 OctetString M + Tunnel-Assignment-Id 82 OctetString M + Tunnel-Preference 83 Unsigned32 M + Tunnel-Client-Auth-Id 90 UTF8String M + Tunnel-Server-Auth-Id 91 UTF8String M + + ;; 8. NAS Accounting + + Accounting-Input-Octets 363 Unsigned64 M + Accounting-Output-Octets 364 Unsigned64 M + Accounting-Input-Packets 365 Unsigned64 M + Accounting-Output-Packets 366 Unsigned64 M + Acct-Session-Time 46 Unsigned32 M + Acct-Authentic 45 Enumerated M + Accounting-Auth-Method 406 Enumerated M + Acct-Delay-Time 41 Unsigned32 M + Acct-Link-Count 51 Unsigned32 M + Acct-Tunnel-Connection 68 OctetString M + Acct-Tunnel-Packets-Lost 86 Unsigned32 M + + ;; 9.3. AVPs Used Only for Compatibility + + NAS-Identifier 32 UTF8String M + NAS-IP-Address 4 OctetString M + NAS-IPv6-Address 95 OctetString M + State 24 OctetString M + ;;Termination-Cause 295 Enumerated M + Origin-AAA-Protocol 408 Enumerated M + +;; =========================================================================== + +@messages + + AAR ::= < Diameter Header: 265, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Request-Type } + [ Destination-Host ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ Port-Limit ] + [ User-Name ] + [ User-Password ] + [ Service-Type ] + [ State ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Auth-Session-State ] + [ Callback-Number ] + [ Called-Station-Id ] + [ Calling-Station-Id ] + [ Originating-Line-Info ] + [ Connect-Info ] + [ CHAP-Auth ] + [ CHAP-Challenge ] + * [ Framed-Compression ] + [ Framed-Interface-Id ] + [ Framed-IP-Address ] + * [ Framed-IPv6-Prefix ] + [ Framed-IP-Netmask ] + [ Framed-MTU ] + [ Framed-Protocol ] + [ ARAP-Password ] + [ ARAP-Security ] + * [ ARAP-Security-Data ] + * [ Login-IP-Host ] + * [ Login-IPv6-Host ] + [ Login-LAT-Group ] + [ Login-LAT-Node ] + [ Login-LAT-Port ] + [ Login-LAT-Service ] + * [ Tunneling ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + AAA ::= < Diameter Header: 265, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Request-Type } + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Service-Type ] + * [ Class ] + * [ Configuration-Token ] + [ Acct-Interim-Interval ] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + [ Idle-Timeout ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Auth-Session-State ] + [ Re-Auth-Request-Type ] + [ Multi-Round-Time-Out ] + [ Session-Timeout ] + [ State ] + * [ Reply-Message ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + * [ Filter-Id ] + [ Password-Retry ] + [ Port-Limit ] + [ Prompt ] + [ ARAP-Challenge-Response ] + [ ARAP-Features ] + [ ARAP-Security ] + * [ ARAP-Security-Data ] + [ ARAP-Zone-Access ] + [ Callback-Id ] + [ Callback-Number ] + [ Framed-AppleTalk-Link ] + * [ Framed-AppleTalk-Network ] + [ Framed-AppleTalk-Zone ] + * [ Framed-Compression ] + [ Framed-Interface-Id ] + [ Framed-IP-Address ] + * [ Framed-IPv6-Prefix ] + [ Framed-IPv6-Pool ] + * [ Framed-IPv6-Route ] + [ Framed-IP-Netmask ] + * [ Framed-Route ] + [ Framed-Pool ] + [ Framed-IPX-Network ] + [ Framed-MTU ] + [ Framed-Protocol ] + [ Framed-Routing ] + * [ Login-IP-Host ] + * [ Login-IPv6-Host ] + [ Login-LAT-Group ] + [ Login-LAT-Node ] + [ Login-LAT-Port ] + [ Login-LAT-Service ] + [ Login-Service ] + [ Login-TCP-Port ] + * [ NAS-Filter-Rule ] + * [ QoS-Filter-Rule ] + * [ Tunneling ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + RAR ::= < Diameter Header: 258, REQ, PXY > + + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + { Re-Auth-Request-Type } + [ User-Name ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + [ Service-Type ] + [ Framed-IP-Address ] + [ Framed-IPv6-Prefix ] + [ Framed-Interface-Id ] + [ Called-Station-Id ] + [ Calling-Station-Id ] + [ Originating-Line-Info ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ State ] + * [ Class ] + [ Reply-Message ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + RAA ::= < Diameter Header: 258, PXY > + + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + [ Service-Type ] + * [ Configuration-Token ] + [ Idle-Timeout ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Re-Auth-Request-Type ] + [ State ] + * [ Class ] + * [ Reply-Message ] + [ Prompt ] + * [ Proxy-Info ] + * [ AVP ] + + STR ::= < Diameter Header: 275, REQ, PXY > + + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Application-Id } + { Termination-Cause } + [ User-Name ] + [ Destination-Host ] + * [ Class ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + STA ::= < Diameter Header: 275, PXY > + + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + * [ Class ] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + ASR ::= < Diameter Header: 274, REQ, PXY > + + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Destination-Host } + { Auth-Application-Id } + [ User-Name ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + [ Service-Type ] + [ Framed-IP-Address ] + [ Framed-IPv6-Prefix ] + [ Framed-Interface-Id ] + [ Called-Station-Id ] + [ Calling-Station-Id ] + [ Originating-Line-Info ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ State ] + * [ Class ] + * [ Reply-Message ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ASA ::= < Diameter Header: 274, PXY > + + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ State] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] + + ACR ::= < Diameter Header: 271, REQ, PXY > + + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ Destination-Host ] + [ Event-Timestamp ] + [ Acct-Delay-Time ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + * [ Class ] + [ Service-Type ] + [ Termination-Cause ] + [ Accounting-Input-Octets ] + [ Accounting-Input-Packets ] + [ Accounting-Output-Octets ] + [ Accounting-Output-Packets ] + [ Acct-Authentic ] + [ Accounting-Auth-Method ] + [ Acct-Link-Count ] + [ Acct-Session-Time ] + [ Acct-Tunnel-Connection ] + [ Acct-Tunnel-Packets-Lost ] + [ Callback-Id ] + [ Callback-Number ] + [ Called-Station-Id ] + [ Calling-Station-Id ] + * [ Connect-Info ] + [ Originating-Line-Info ] + [ Authorization-Lifetime ] + [ Session-Timeout ] + [ Idle-Timeout ] + [ Port-Limit ] + [ Accounting-Realtime-Required ] + [ Acct-Interim-Interval ] + * [ Filter-Id ] + * [ NAS-Filter-Rule ] + * [ QoS-Filter-Rule ] + [ Framed-AppleTalk-Link ] + [ Framed-AppleTalk-Network ] + [ Framed-AppleTalk-Zone ] + [ Framed-Compression ] + [ Framed-Interface-Id ] + [ Framed-IP-Address ] + [ Framed-IP-Netmask ] + * [ Framed-IPv6-Prefix ] + [ Framed-IPv6-Pool ] + * [ Framed-IPv6-Route ] + [ Framed-IPX-Network ] + [ Framed-MTU ] + [ Framed-Pool ] + [ Framed-Protocol ] + * [ Framed-Route ] + [ Framed-Routing ] + * [ Login-IP-Host ] + * [ Login-IPv6-Host ] + [ Login-LAT-Group ] + [ Login-LAT-Node ] + [ Login-LAT-Port ] + [ Login-LAT-Service ] + [ Login-Service ] + [ Login-TCP-Port ] + * [ Tunneling ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ACA ::= < Diameter Header: 271, PXY > + + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + { Accounting-Record-Type } + { Accounting-Record-Number } + [ Acct-Application-Id ] + [ Vendor-Specific-Application-Id ] + [ User-Name ] + [ Accounting-Sub-Session-Id ] + [ Acct-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Event-Timestamp ] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + [ Origin-AAA-Protocol ] + [ Origin-State-Id ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + [ Service-Type ] + [ Termination-Cause ] + [ Accounting-Realtime-Required ] + [ Acct-Interim-Interval ] + * [ Class ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + +;; =========================================================================== + +@grouped + + CHAP-Auth ::= < AVP Header: 402 > + + { CHAP-Algorithm } + { CHAP-Ident } + [ CHAP-Response ] + * [ AVP ] + + Tunneling ::= < AVP Header: 401 > + + { Tunnel-Type } + { Tunnel-Medium-Type } + { Tunnel-Client-Endpoint } + { Tunnel-Server-Endpoint } + [ Tunnel-Preference ] + [ Tunnel-Client-Auth-Id ] + [ Tunnel-Server-Auth-Id ] + [ Tunnel-Assignment-Id ] + [ Tunnel-Password ] + [ Tunnel-Private-Group-Id ] + +;; =========================================================================== + +@enum NAS-Port-Type + + ASYNC 0 + SYNC 1 + ISDN_SYNC 2 + ISDN_ASYNC_V120 3 + ISDN_ASYNC_V110 4 + VIRTUAL 5 + PIAFS 6 + HDLC_CLEAR_CHANNEL 7 + X25 8 + X75 9 + G3FAX 10 + SDSL 11 + ADSL-CAP 12 + ADSL-DMT 13 + IDSL 14 + ETHERNET 15 + XDSL 16 + CABLE 17 + WIRELESS_OTHER 18 + 'WIRELESS_802.11' 19 + TOKEN-RING 20 + FDDI 21 + WIRELESS_CDMA2000 22 + WIRELESS_UMTS 23 + WIRELESS_1X-EV 24 + IAPP 25 + +@enum Prompt + + NO_ECHO 0 + ECHO 1 + +@enum CHAP-Algorithm + + WITH_MD5 5 + +@enum Service-Type + + LOGIN 1 + FRAMED 2 + CALLBACK_LOGIN 3 + CALLBACK_FRAMED 4 + OUTBOUND 5 + ADMINISTRATIVE 6 + NAS_PROMPT 7 + AUTHENTICATE_ONLY 8 + CALLBACK_NAS_PROMPT 9 + CALL_CHECK 10 + CALLBACK_ADMINISTRATIVE 11 + VOICE 12 + FAX 13 + MODEM_RELAY 14 + IAPP-REGISTER 15 + IAPP-AP-CHECK 16 + AUTHORIZE_ONLY 17 + +@enum Framed-Protocol + + PPP 1 + SLIP 2 + ARAP 3 + GANDALF 4 + XYLOGICS 5 + X75 6 + +@enum Framed-Routing + + NONE 0 + SEND 1 + LISTEN 2 + SEND_AND_LISTEN 3 + +@enum Framed-Compression + + NONE 0 + VJ 1 + IPX 2 + STAC-LZS 3 + +@enum ARAP-Zone-Access + + DEFAULT 1 + FILTER_INCLUSIVELY 2 + FILTER_EXCLUSIVELY 4 + +@enum Login-Service + + TELNET 0 + RLOGIN 1 + TCP_CLEAR 2 + PORTMASTER 3 + LAT 4 + X25-PAD 5 + X25-T3POS 6 + TCP_CLEAR_QUIET 8 + +@enum Tunnel-Type + + PPTP 1 + L2F 2 + L2TP 3 + ATMP 4 + VTP 5 + AH 6 + IP-IP 7 + MIN-IP-IP 8 + ESP 9 + GRE 10 + DVS 11 + IP-IN-IP 12 + VLAN 13 + +@enum Tunnel-Medium-Type + + IPV4 1 + IPV6 2 + NSAP 3 + HDLC 4 + BBN_1822 5 + '802' 6 + E163 7 + E164 8 + F69 9 + X121 10 + IPX 11 + APPLETALK 12 + DECNET_IV 13 + BANYAN_VINES 14 + E164_NSAP 15 + + +@enum Acct-Authentic + + RADIUS 1 + LOCAL 2 + REMOTE 3 + DIAMETER 4 + +@enum Accounting-Auth-Method + + PAP 1 + CHAP 2 + MS-CHAP-1 3 + MS-CHAP-2 4 + EAP 5 + NONE 7 + +@enum Termination-Cause + + USER_REQUEST 11 + LOST_CARRIER 12 + LOST_SERVICE 13 + IDLE_TIMEOUT 14 + SESSION_TIMEOUT 15 + ADMIN_RESET 16 + ADMIN_REBOOT 17 + PORT_ERROR 18 + NAS_ERROR 19 + NAS_REQUEST 20 + NAS_REBOOT 21 + PORT_UNNEEDED 22 + PORT_PREEMPTED 23 + PORT_SUSPENDED 24 + SERVICE_UNAVAILABLE 25 + CALLBACK 26 + USER_ERROR 27 + HOST_REQUEST 28 + SUPPLICANT_RESTART 29 + REAUTHORIZATION_FAILURE 30 + PORT_REINIT 31 + PORT_DISABLED 32 diff --git a/lib/diameter/examples/dict/rfc4006_cc.dia b/lib/diameter/examples/dict/rfc4006_cc.dia new file mode 100644 index 0000000000..b723e4ddbb --- /dev/null +++ b/lib/diameter/examples/dict/rfc4006_cc.dia @@ -0,0 +1,349 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4006, Diameter Credit-Control Application +;; + +@id 4 + +@inherits rfc3588_base +@inherits rfc4005_nas Filter-Id + +;; =========================================================================== + +@avp_types + + CC-Correlation-Id 411 OctetString - + CC-Input-Octets 412 Unsigned64 M + CC-Money 413 Grouped M + CC-Output-Octets 414 Unsigned64 M + CC-Request-Number 415 Unsigned32 M + CC-Request-Type 416 Enumerated M + CC-Service-Specific-Units 417 Unsigned64 M + CC-Session-Failover 418 Enumerated M + CC-Sub-Session-Id 419 Unsigned64 M + CC-Time 420 Unsigned32 M + CC-Total-Octets 421 Unsigned64 M + CC-Unit-Type 454 Enumerated M + Check-Balance-Result 422 Enumerated M + Cost-Information 423 Grouped M + Cost-Unit 424 UTF8String M + Credit-Control 426 Enumerated M + Credit-Control-Failure-Handling 427 Enumerated M + Currency-Code 425 Unsigned32 M + Direct-Debiting-Failure-Handling 428 Enumerated M + Exponent 429 Integer32 M + Final-Unit-Action 449 Enumerated M + Final-Unit-Indication 430 Grouped M + Granted-Service-Unit 431 Grouped M + G-S-U-Pool-Identifier 453 Unsigned32 M + G-S-U-Pool-Reference 457 Grouped M + Multiple-Services-Credit-Control 456 Grouped M + Multiple-Services-Indicator 455 Enumerated M + Rating-Group 432 Unsigned32 M + Redirect-Address-Type 433 Enumerated M + Redirect-Server 434 Grouped M + Redirect-Server-Address 435 UTF8String M + Requested-Action 436 Enumerated M + Requested-Service-Unit 437 Grouped M + Restriction-Filter-Rule 438 IPFilterRule M + Service-Context-Id 461 UTF8String M + Service-Identifier 439 Unsigned32 M + Service-Parameter-Info 440 Grouped - + Service-Parameter-Type 441 Unsigned32 - + Service-Parameter-Value 442 OctetString - + Subscription-Id 443 Grouped M + Subscription-Id-Data 444 UTF8String M + Subscription-Id-Type 450 Enumerated M + Tariff-Change-Usage 452 Enumerated M + Tariff-Time-Change 451 Time M + Unit-Value 445 Grouped M + Used-Service-Unit 446 Grouped M + User-Equipment-Info 458 Grouped - + User-Equipment-Info-Type 459 Enumerated - + User-Equipment-Info-Value 460 OctetString - + Value-Digits 447 Integer64 M + Validity-Time 448 Unsigned32 M + +;; =========================================================================== + +@messages + + CCR ::= < Diameter Header: 272, REQ, PXY > + + < Session-Id > + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Application-Id } + { Service-Context-Id } + { CC-Request-Type } + { CC-Request-Number } + [ Destination-Host ] + [ User-Name ] + [ CC-Sub-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Origin-State-Id ] + [ Event-Timestamp ] + * [ Subscription-Id ] + [ Service-Identifier ] + [ Termination-Cause ] + [ Requested-Service-Unit ] + [ Requested-Action ] + * [ Used-Service-Unit ] + [ Multiple-Services-Indicator ] + * [ Multiple-Services-Credit-Control ] + * [ Service-Parameter-Info ] + [ CC-Correlation-Id ] + [ User-Equipment-Info ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + CCA ::= < Diameter Header: 272, PXY > + + < Session-Id > + { Result-Code } + { Origin-Host } + { Origin-Realm } + { Auth-Application-Id } + { CC-Request-Type } + { CC-Request-Number } + [ User-Name ] + [ CC-Session-Failover ] + [ CC-Sub-Session-Id ] + [ Acct-Multi-Session-Id ] + [ Origin-State-Id ] + [ Event-Timestamp ] + [ Granted-Service-Unit ] + * [ Multiple-Services-Credit-Control ] + [ Cost-Information] + [ Final-Unit-Indication ] + [ Check-Balance-Result ] + [ Credit-Control-Failure-Handling ] + [ Direct-Debiting-Failure-Handling ] + [ Validity-Time] + * [ Redirect-Host] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ Failed-AVP ] + * [ AVP ] + +;; =========================================================================== + +@grouped + + Cost-Information ::= < AVP Header: 423 > + + { Unit-Value } + { Currency-Code } + [ Cost-Unit ] + + Unit-Value ::= < AVP Header: 445 > + + { Value-Digits } + [ Exponent ] + + Multiple-Services-Credit-Control ::= < AVP Header: 456 > + + [ Granted-Service-Unit ] + [ Requested-Service-Unit ] + * [ Used-Service-Unit ] + [ Tariff-Change-Usage ] + * [ Service-Identifier ] + [ Rating-Group ] + * [ G-S-U-Pool-Reference ] + [ Validity-Time ] + [ Result-Code ] + [ Final-Unit-Indication ] + * [ AVP ] + + Granted-Service-Unit ::= < AVP Header: 431 > + + [ Tariff-Time-Change ] + [ CC-Time ] + [ CC-Money ] + [ CC-Total-Octets ] + [ CC-Input-Octets ] + [ CC-Output-Octets ] + [ CC-Service-Specific-Units ] + * [ AVP ] + + Requested-Service-Unit ::= < AVP Header: 437 > + + [ CC-Time ] + [ CC-Money ] + [ CC-Total-Octets ] + [ CC-Input-Octets ] + [ CC-Output-Octets ] + [ CC-Service-Specific-Units ] + * [ AVP ] + + Used-Service-Unit ::= < AVP Header: 446 > + + [ Tariff-Change-Usage ] + [ CC-Time ] + [ CC-Money ] + [ CC-Total-Octets ] + [ CC-Input-Octets ] + [ CC-Output-Octets ] + [ CC-Service-Specific-Units ] + * [ AVP ] + + CC-Money ::= < AVP Header: 413 > + + { Unit-Value } + [ Currency-Code ] + + G-S-U-Pool-Reference ::= < AVP Header: 457 > + + { G-S-U-Pool-Identifier } + { CC-Unit-Type } + { Unit-Value } + + Final-Unit-Indication ::= < AVP Header: 430 > + + { Final-Unit-Action } + * [ Restriction-Filter-Rule ] + * [ Filter-Id ] + [ Redirect-Server ] + + Redirect-Server ::= < AVP Header: 434 > + + { Redirect-Address-Type } + { Redirect-Server-Address } + + Service-Parameter-Info ::= < AVP Header: 440 > + + { Service-Parameter-Type } + { Service-Parameter-Value } + + Subscription-Id ::= < AVP Header: 443 > + + { Subscription-Id-Type } + { Subscription-Id-Data } + + User-Equipment-Info ::= < AVP Header: 458 > + + { User-Equipment-Info-Type } + { User-Equipment-Info-Value } + +;; =========================================================================== + +@enum CC-Request-Type + + INITIAL_REQUEST 1 + UPDATE_REQUEST 2 + TERMINATION_REQUEST 3 + EVENT_REQUEST 4 + +@enum CC-Session-Failover + + NOT_SUPPORTED 0 + SUPPORTED 1 + +@enum Check-Balance-Result + + ENOUGH_CREDIT 0 + NO_CREDIT 1 + +@enum Credit-Control + + AUTHORIZATION 0 + RE_AUTHORIZATION 1 + +@enum Credit-Control-Failure-Handling + + TERMINATE 0 + CONTINUE 1 + RETRY_AND_TERMINATE 2 + +@enum Direct-Debiting-Failure-Handling + + TERMINATE_OR_BUFFER 0 + CONTINUE 1 + +@enum Tariff-Change-Usage + + UNIT_BEFORE_TARIFF_CHANGE 0 + UNIT_AFTER_TARIFF_CHANGE 1 + UNIT_INDETERMINATE 2 + +@enum CC-Unit-Type + + TIME 0 + MONEY 1 + TOTAL-OCTETS 2 + INPUT-OCTETS 3 + OUTPUT-OCTETS 4 + SERVICE-SPECIFIC-UNITS 5 + +@enum Final-Unit-Action + + TERMINATE 0 + REDIRECT 1 + RESTRICT_ACCESS 2 + +@enum Redirect-Address-Type + + IPV4 0 + IPV6 1 + URL 2 + SIP_URI 3 + +@enum Multiple-Services-Indicator + + NOT_SUPPORTED 0 + SUPPORTED 1 + +@enum Requested-Action + + DIRECT_DEBITING 0 + REFUND_ACCOUNT 1 + CHECK_BALANCE 2 + PRICE_ENQUIRY 3 + +@enum Subscription-Id-Type + + END_USER_E164 0 + END_USER_IMSI 1 + END_USER_SIP_URI 2 + END_USER_NAI 3 + END_USER_PRIVATE 4 + +@enum User-Equipment-Info-Type + + IMEISV 0 + MAC 1 + EUI64 2 + MODIFIED_EUI64 3 + +;; =========================================================================== + +@define Result-Code + + END_USER_SERVICE_DENIED 4010 + CREDIT_CONTROL_NOT_APPLICABLE 4011 + CREDIT_LIMIT_REACHED 4012 + + USER_UNKNOWN 5030 + RATING_FAILED 5031 diff --git a/lib/diameter/examples/dict/rfc4072_eap.dia b/lib/diameter/examples/dict/rfc4072_eap.dia new file mode 100644 index 0000000000..111516b347 --- /dev/null +++ b/lib/diameter/examples/dict/rfc4072_eap.dia @@ -0,0 +1,150 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4072, Diameter Extensible Authentication Protocol (EAP) Application +;; +;; Edits: +;; +;; - Move EAP-Payload to not break fixed/required/optional order +;; - Framed-Appletalk-Link -> Framed-AppleTalk-Link +;; - Framed-Appletalk-Network -> Framed-AppleTalk-Network +;; - Framed-Appletalk-Zone -> Framed-AppleTalk-Zone +;; + +@id 5 + +@inherits rfc3588_base +@inherits rfc4005_nas + +;; =========================================================================== + +@avp_types + + EAP-Master-Session-Key 464 OctetString - + EAP-Key-Name 102 OctetString - + EAP-Payload 462 OctetString - + EAP-Reissued-Payload 463 OctetString - + Accounting-EAP-Auth-Method 465 Unsigned64 - + +;; =========================================================================== + +@messages + + DER ::= < Diameter Header: 268, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { Auth-Request-Type } + { EAP-Payload } + [ Destination-Host ] + [ NAS-Identifier ] + [ NAS-IP-Address ] + [ NAS-IPv6-Address ] + [ NAS-Port ] + [ NAS-Port-Id ] + [ NAS-Port-Type ] + [ Origin-State-Id ] + [ Port-Limit ] + [ User-Name ] + [ EAP-Key-Name ] + [ Service-Type ] + [ State ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Auth-Session-State ] + [ Callback-Number ] + [ Called-Station-Id ] + [ Calling-Station-Id ] + [ Originating-Line-Info ] + [ Connect-Info ] + * [ Framed-Compression ] + [ Framed-Interface-Id ] + [ Framed-IP-Address ] + * [ Framed-IPv6-Prefix ] + [ Framed-IP-Netmask ] + [ Framed-MTU ] + [ Framed-Protocol ] + * [ Tunneling ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + DEA ::= < Diameter Header: 268, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Request-Type } + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ EAP-Payload ] + [ EAP-Reissued-Payload ] + [ EAP-Master-Session-Key ] + [ EAP-Key-Name ] + [ Multi-Round-Time-Out ] + [ Accounting-EAP-Auth-Method ] + [ Service-Type ] + * [ Class ] + * [ Configuration-Token ] + [ Acct-Interim-Interval ] + [ Error-Message ] + [ Error-Reporting-Host ] + * [ Failed-AVP ] + [ Idle-Timeout ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Auth-Session-State ] + [ Re-Auth-Request-Type ] + [ Session-Timeout ] + [ State ] + * [ Reply-Message ] + [ Origin-State-Id ] + * [ Filter-Id ] + [ Port-Limit ] + [ Callback-Id ] + [ Callback-Number ] + [ Framed-AppleTalk-Link ] + * [ Framed-AppleTalk-Network ] + [ Framed-AppleTalk-Zone ] + * [ Framed-Compression ] + [ Framed-Interface-Id ] + [ Framed-IP-Address ] + * [ Framed-IPv6-Prefix ] + [ Framed-IPv6-Pool ] + * [ Framed-IPv6-Route ] + [ Framed-IP-Netmask ] + * [ Framed-Route ] + [ Framed-Pool ] + [ Framed-IPX-Network ] + [ Framed-MTU ] + [ Framed-Protocol ] + [ Framed-Routing ] + * [ NAS-Filter-Rule ] + * [ QoS-Filter-Rule ] + * [ Tunneling ] + * [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ AVP ] diff --git a/lib/diameter/examples/dict/rfc4590_digest.dia b/lib/diameter/examples/dict/rfc4590_digest.dia new file mode 100644 index 0000000000..a4ebe0c456 --- /dev/null +++ b/lib/diameter/examples/dict/rfc4590_digest.dia @@ -0,0 +1,45 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4590, RADIUS Extension for Digest Authentication +;; + +@avp_types + + Digest-Response 103 OctetString - + Digest-Realm 104 OctetString - + Digest-Nonce 105 OctetString - + Digest-Response-Auth 106 OctetString - + Digest-Nextnonce 107 OctetString - + Digest-Method 108 OctetString - + Digest-URI 109 OctetString - + Digest-Qop 110 OctetString - + Digest-Algorithm 111 OctetString - + Digest-Entity-Body-Hash 112 OctetString - + Digest-CNonce 113 OctetString - + Digest-Nonce-Count 114 OctetString - + Digest-Username 115 OctetString - + Digest-Opaque 116 OctetString - + Digest-Auth-Param 117 OctetString - + Digest-AKA-Auts 118 OctetString - + Digest-Domain 119 OctetString - + Digest-Stale 120 OctetString - + Digest-HA1 121 OctetString - + SIP-AOR 122 OctetString - diff --git a/lib/diameter/examples/dict/rfc4740_sip.dia b/lib/diameter/examples/dict/rfc4740_sip.dia new file mode 100644 index 0000000000..8c21882649 --- /dev/null +++ b/lib/diameter/examples/dict/rfc4740_sip.dia @@ -0,0 +1,446 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +;; +;; RFC 4740, Diameter Session Initiation Protocol (SIP) Application +;; + +@id 6 + +@inherits rfc3588_base +@inherits rfc4590_digest + +;; =========================================================================== + +@avp_types + + SIP-Accounting-Information 368 Grouped M + SIP-Accounting-Server-URI 369 DiameterURI M + SIP-Credit-Control-Server-URI 370 DiameterURI M + SIP-Server-URI 371 UTF8String M + SIP-Server-Capabilities 372 Grouped M + SIP-Mandatory-Capability 373 Unsigned32 M + SIP-Optional-Capability 374 Unsigned32 M + SIP-Server-Assignment-Type 375 Enumerated M + SIP-Auth-Data-Item 376 Grouped M + SIP-Authentication-Scheme 377 Enumerated M + SIP-Item-Number 378 Unsigned32 M + SIP-Authenticate 379 Grouped M + SIP-Authorization 380 Grouped M + SIP-Authentication-Info 381 Grouped M + SIP-Number-Auth-Items 382 Unsigned32 M + SIP-Deregistration-Reason 383 Grouped M + SIP-Reason-Code 384 Enumerated M + SIP-Reason-Info 385 UTF8String M + SIP-Visited-Network-Id 386 UTF8String M + SIP-User-Authorization-Type 387 Enumerated M + SIP-Supported-User-Data-Type 388 UTF8String M + SIP-User-Data 389 Grouped M + SIP-User-Data-Type 390 UTF8String M + SIP-User-Data-Contents 391 OctetString M + SIP-User-Data-Already-Available 392 Enumerated M + SIP-Method 393 UTF8String M + +;; =========================================================================== + +@messages + + ;; 8.1. User-Authorization-Request + + UAR ::= < Diameter Header: 283, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { SIP-AOR } + [ Destination-Host ] + [ User-Name ] + [ SIP-Visited-Network-Id ] + [ SIP-User-Authorization-Type ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.2. User-Authorization-Answer + + UAA ::= < Diameter Header: 283, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Result-Code } + { Origin-Host } + { Origin-Realm } + [ SIP-Server-URI ] + [ SIP-Server-Capabilities ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.3. Server-Assignment-Request + + SAR ::= < Diameter Header: 284, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { SIP-Server-Assignment-Type } + { SIP-User-Data-Already-Available } + [ Destination-Host ] + [ User-Name ] + [ SIP-Server-URI ] + * [ SIP-Supported-User-Data-Type ] + * [ SIP-AOR ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.4. Server-Assignment-Answer + + SAA ::= < Diameter Header: 284, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + * [ SIP-User-Data ] + [ SIP-Accounting-Information ] + * [ SIP-Supported-User-Data-Type ] + [ User-Name ] + [ Auth-Grace-Period ] + [ Authorization-Lifetime ] + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.5. Location-Info-Request + + LIR ::= < Diameter Header: 285, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { SIP-AOR } + [ Destination-Host ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.6. Location-Info-Answer + + LIA ::= < Diameter Header: 285, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + [ SIP-Server-URI ] + [ SIP-Server-Capabilities ] + [ Auth-Grace-Period ] + [ Authorization-Lifetime ] + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.7. Multimedia-Auth-Request + + MAR ::= < Diameter Header: 286, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { SIP-AOR } + { SIP-Method } + [ Destination-Host ] + [ User-Name ] + [ SIP-Server-URI ] + [ SIP-Number-Auth-Items ] + [ SIP-Auth-Data-Item ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.8. Multimedia-Auth-Answer + + MAA ::= < Diameter Header: 286, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + [ User-Name ] + [ SIP-AOR ] + [ SIP-Number-Auth-Items ] + * [ SIP-Auth-Data-Item ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.9. Registration-Termination-Request + + RTR ::= < Diameter Header: 287, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Host } + { SIP-Deregistration-Reason } + [ Destination-Realm ] + [ User-Name ] + * [ SIP-AOR ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.10. Registration-Termination-Answer + + RTA ::= < Diameter Header: 287, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.11. Push-Profile-Request + + PPR ::= < Diameter Header: 288, REQ, PXY > + + < Session-Id > + { Auth-Application-Id } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + { Destination-Realm } + { User-Name } + * [ SIP-User-Data ] + [ SIP-Accounting-Information ] + [ Destination-Host ] + [ Authorization-Lifetime ] + [ Auth-Grace-Period ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + + ;; 8.12. Push-Profile-Answer + + PPA ::= < Diameter Header: 288, PXY > + + < Session-Id > + { Auth-Application-Id } + { Result-Code } + { Auth-Session-State } + { Origin-Host } + { Origin-Realm } + [ Redirect-Host ] + [ Redirect-Host-Usage ] + [ Redirect-Max-Cache-Time ] + * [ Proxy-Info ] + * [ Route-Record ] + * [ AVP ] + +;; =========================================================================== + +@grouped + + SIP-Accounting-Information ::= < AVP Header: 368 > + + * [ SIP-Accounting-Server-URI ] + * [ SIP-Credit-Control-Server-URI ] + * [ AVP] + + SIP-Server-Capabilities ::= < AVP Header: 372 > + + * [ SIP-Mandatory-Capability ] + * [ SIP-Optional-Capability ] + * [ SIP-Server-URI ] + * [ AVP ] + + SIP-Auth-Data-Item ::= < AVP Header: 376 > + + { SIP-Authentication-Scheme } + [ SIP-Item-Number ] + [ SIP-Authenticate ] + [ SIP-Authorization ] + [ SIP-Authentication-Info ] + * [ AVP ] + + SIP-Authenticate ::= < AVP Header: 379 > + + { Digest-Realm } + { Digest-Nonce } + [ Digest-Domain ] + [ Digest-Opaque ] + [ Digest-Stale ] + [ Digest-Algorithm ] + [ Digest-Qop ] + [ Digest-HA1] + * [ Digest-Auth-Param ] + * [ AVP ] + + SIP-Authorization ::= < AVP Header: 380 > + + { Digest-Username } + { Digest-Realm } + { Digest-Nonce } + { Digest-URI } + { Digest-Response } + [ Digest-Algorithm ] + [ Digest-CNonce ] + [ Digest-Opaque ] + [ Digest-Qop ] + [ Digest-Nonce-Count ] + [ Digest-Method] + [ Digest-Entity-Body-Hash ] + * [ Digest-Auth-Param ] + * [ AVP ] + + SIP-Authentication-Info ::= < AVP Header: 381 > + + [ Digest-Nextnonce ] + [ Digest-Qop ] + [ Digest-Response-Auth ] + [ Digest-CNonce ] + [ Digest-Nonce-Count ] + * [ AVP ] + + SIP-Deregistration-Reason ::= < AVP Header: 383 > + + { SIP-Reason-Code } + [ SIP-Reason-Info ] + * [ AVP ] + + SIP-User-Data ::= < AVP Header: 389 > + + { SIP-User-Data-Type } + { SIP-User-Data-Contents } + * [ AVP ] + +;; =========================================================================== + +@enum SIP-Server-Assignment-Type + + NO_ASSIGNMENT 0 + REGISTRATION 1 + RE_REGISTRATION 2 + UNREGISTERED_USER 3 + TIMEOUT_DEREGISTRATION 4 + USER_DEREGISTRATION 5 + TIMEOUT_DEREGISTRATION_STORE 6 + USER_DEREGISTRATION_STORE 7 + ADMINISTRATIVE_DEREGISTRATION 8 + AUTHENTICATION_FAILURE 9 + AUTHENTICATION_TIMEOUT 10 + DEREGISTRATION_TOO_MUCH_DATA 11 + +@enum SIP-Authentication-Scheme + + DIGEST 0 + +@enum SIP-Reason-Code + + PERMANENT_TERMINATION 0 + NEW_SIP_SERVER_ASSIGNED 1 + SIP_SERVER_CHANGE 2 + REMOVE_SIP_SERVER 3 + +@enum SIP-User-Authorization-Type + + REGISTRATION 0 + DEREGISTRATION 1 + REGISTRATION_AND_CAPABILITIES 2 + +@enum SIP-User-Data-Already-Available + + USER_DATA_NOT_AVAILABLE 0 + USER_DATA_ALREADY_AVAILABLE 1 + +;; =========================================================================== + +@define Result-Code + + ;; Success + + FIRST_REGISTRATION 2003 + SUBSEQUENT_REGISTRATION 2004 + UNREGISTERED_SERVICE 2005 + SUCCESS_SERVER_NAME_NOT_STORED 2006 + SERVER_SELECTION 2007 + SUCCESS_AUTH_SENT_SERVER_NOT_STORED 2008 + + ;; Transient Failures + + USER_NAME_REQUIRED 4013 + + ;; Permanent Failures + + USER_UNKNOWN 5032 + IDENTITIES_DONT_MATCH 5033 + IDENTITY_NOT_REGISTERED 5034 + ROAMING_NOT_ALLOWED 5035 + IDENTITY_ALREADY_REGISTERED 5036 + AUTH_SCHEME_NOT_SUPPORTED 5037 + IN_ASSIGNMENT_TYPE 5038 + TOO_MUCH_DATA 5039 + NOT_SUPPORTED_USER_DATA 5040 diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl index 0fa7fd406f..4273262015 100644 --- a/lib/diameter/include/diameter.hrl +++ b/lib/diameter/include/diameter.hrl @@ -107,6 +107,21 @@ transport = sctp, %% | tcp, protocol = diameter}). %% | radius | 'tacacs+' +%% A diameter_callback record can be specified as an application +%% module in order to selectively receive callbacks or alter their +%% form. +-record(diameter_callback, + {peer_up, + peer_down, + pick_peer, + prepare_request, + prepare_retransmit, + handle_request, + handle_answer, + handle_error, + default, + extra = []}). + %% The diameter service and diameter_apps records are only passed %% through the transport interface when starting a transport process, %% although typically a transport implementation will (and probably diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index eea2aa894d..dbfaa4e140 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -54,14 +54,16 @@ VPATH = .:base:compiler:transport:gen include modules.mk +# Modules generated from dictionary specifications. DICT_MODULES = $(DICTS:%=gen/diameter_gen_%) DICT_ERLS = $(DICT_MODULES:%=%.erl) DICT_HRLS = $(DICT_MODULES:%=%.hrl) # Modules to build before compiling dictionaries. -COMPILER_MODULES = $(filter compiler/%, $(CT_MODULES)) +COMPILER_MODULES = $(notdir $(filter compiler/%, $(CT_MODULES))) \ + $(DICT_YRL) -# All handwritten modules. +# All handwritten modules from which a depend.mk is generated. MODULES = \ $(RT_MODULES) \ $(CT_MODULES) @@ -74,17 +76,21 @@ APP_MODULES = \ # Modules for which to build beams. TARGET_MODULES = \ $(APP_MODULES) \ - $(CT_MODULES) + $(CT_MODULES) \ + $(DICT_YRL:%=gen/%) # What to build for the 'opt' target. TARGET_FILES = \ - $(patsubst %,$(EBIN)/%.$(EMULATOR),$(notdir $(TARGET_MODULES))) \ + $(patsubst %, $(EBIN)/%.$(EMULATOR), $(notdir $(TARGET_MODULES))) \ $(APP_TARGET) \ $(APPUP_TARGET) # Subdirectories of src to release modules into. TARGET_DIRS = $(sort $(dir $(TARGET_MODULES))) +# Ditto for examples. +EXAMPLE_DIRS = $(sort $(dir $(EXAMPLES))) + APP_FILE = diameter.app APP_SRC = $(APP_FILE).src APP_TARGET = $(EBIN)/$(APP_FILE) @@ -125,6 +131,10 @@ opt: $(TARGET_FILES) debug: @$(MAKE) TYPE=debug opt +# The dictionary parser. +gen/$(DICT_YRL).erl: compiler/$(DICT_YRL).yrl + $(ERLC) -Werror -o $(@D) $< + # Generate the app file. $(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk M=`echo $(notdir $(APP_MODULES)) | tr ' ' ,`; \ @@ -146,6 +156,8 @@ info: @echo ======================================== @$(call list,DICTS) @echo + @$(call list,DICT_YRL) + @echo @$(call list,RT_MODULES) @echo @$(call list,CT_MODULES) @@ -160,11 +172,13 @@ info: @echo @$(call list,EXAMPLES) @echo + @$(call list,EXAMPLE_DIRS) + @echo @$(call list,BINS) @echo ======================================== clean: - rm -f $(TARGET_FILES) $(DICT_ERLS) $(DICT_HRLS) + rm -f $(TARGET_FILES) gen/* rm -f depend.mk # ---------------------------------------------------- @@ -180,22 +194,29 @@ endif # Can't $(INSTALL_DIR) more than one directory at a time on Solaris. release_spec: opt - for d in bin ebin examples include src/dict $(TARGET_DIRS:%/=src/%); do \ + for d in bin ebin include src/dict; do \ $(INSTALL_DIR) $(RELSYSDIR)/$$d; \ done $(INSTALL_SCRIPT) $(BINS:%=../bin/%) $(RELSYSDIR)/bin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin - $(INSTALL_DATA) $(EXAMPLES:%=../examples/%) $(RELSYSDIR)/examples $(INSTALL_DATA) $(EXTERNAL_HRLS:%=../include/%) $(DICT_HRLS) \ $(RELSYSDIR)/include $(INSTALL_DATA) $(DICTS:%=dict/%.dia) $(RELSYSDIR)/src/dict $(MAKE) $(TARGET_DIRS:%/=release_src_%) + $(MAKE) $(EXAMPLE_DIRS:%/=release_examples_%) $(TARGET_DIRS:%/=release_src_%): release_src_%: - $(INSTALL_DATA) $(filter $*/%,$(TARGET_MODULES:%=%.erl) \ - $(INTERNAL_HRLS)) \ + $(INSTALL_DIR) $(RELSYSDIR)/src/$* + $(INSTALL_DATA) $(filter $*/%, $(TARGET_MODULES:%=%.erl) \ + $(INTERNAL_HRLS)) \ + $(filter $*/%, compiler/$(DICT_YRL).yrl) \ $(RELSYSDIR)/src/$* +$(EXAMPLE_DIRS:%/=release_examples_%): release_examples_%: + $(INSTALL_DIR) $(RELSYSDIR)/examples/$* + $(INSTALL_DATA) $(patsubst %, ../examples/%, $(filter $*/%, $(EXAMPLES))) \ + $(RELSYSDIR)/examples/$* + release_docs_spec: # ---------------------------------------------------- @@ -207,7 +228,7 @@ gen/diameter_gen_base_accounting.hrl gen/diameter_gen_relay.hrl: \ $(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR) gen/diameter_gen_base_rfc3588.erl gen/diameter_gen_base_rfc3588.hrl: \ - $(COMPILER_MODULES:compiler/%=$(EBIN)/%.$(EMULATOR)) + $(COMPILER_MODULES:%=$(EBIN)/%.$(EMULATOR)) $(DICT_MODULES:gen/%=$(EBIN)/%.$(EMULATOR)): \ $(INCDIR)/diameter.hrl \ @@ -224,10 +245,13 @@ depend.mk: depend.sed $(MODULES:%=%.erl) Makefile -include depend.mk -.PRECIOUS: $(DICT_ERLS) $(DICT_HRLS) .PHONY: app clean depend dict info release_subdir .PHONY: debug opt release_docs_spec release_spec .PHONY: $(TARGET_DIRS:%/=%) $(TARGET_DIRS:%/=release_src_%) +.PHONY: $(EXAMPLE_DIRS:%/=release_examples_%) + +# Keep intermediate files. +.SECONDARY: $(DICT_ERLS) $(DICT_HRLS) gen/$(DICT_YRL:%=%.erl) # ---------------------------------------------------- # Targets using secondary expansion (make >= 3.81) @@ -237,4 +261,6 @@ depend.mk: depend.sed $(MODULES:%=%.erl) Makefile # Make beams from a subdirectory. $(TARGET_DIRS:%/=%): \ - $$(patsubst $$@/%,$(EBIN)/%.$(EMULATOR),$$(filter $$@/%,$(TARGET_MODULES))) + $$(patsubst $$@/%, \ + $(EBIN)/%.$(EMULATOR), \ + $$(filter $$@/%, $(TARGET_MODULES) compiler/$(DICT_YRL))) diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 2f721421d8..336f0c1f2d 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -38,17 +38,47 @@ service_info/2]). %% Start/stop the application. In a "real" application this should -%% typically be a consequence of specifying diameter in a release file -%% rather than by calling start/stop explicitly. +%% typically be a consequence of a release file rather than by calling +%% start/stop explicitly. -export([start/0, stop/0]). +-export_type([evaluable/0, + app_alias/0, + service_name/0, + capability/0, + peer_filter/0, + service_opt/0, + application_opt/0, + app_module/0, + transport_ref/0, + transport_opt/0, + transport_pred/0, + call_opt/0]). + +-export_type(['OctetString'/0, + 'Integer32'/0, + 'Integer64'/0, + 'Unsigned32'/0, + 'Unsigned64'/0, + 'Float32'/0, + 'Float64'/0, + 'Grouped'/0, + 'Address'/0, + 'Time'/0, + 'UTF8String'/0, + 'DiameterIdentity'/0, + 'DiameterURI'/0, + 'Enumerated'/0, + 'IPFilterRule'/0, + 'QoSFilterRule'/0]). + +-include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_types.hrl"). -%%% -------------------------------------------------------------------------- -%%% start/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% start/0 +%% --------------------------------------------------------------------------- -spec start() -> ok @@ -57,9 +87,9 @@ start() -> application:start(?APPLICATION). -%%% -------------------------------------------------------------------------- -%%% stop/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% stop/0 +%% --------------------------------------------------------------------------- -spec stop() -> ok @@ -68,9 +98,9 @@ start() -> stop() -> application:stop(?APPLICATION). -%%% -------------------------------------------------------------------------- -%%% start_service/2 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% start_service/2 +%% --------------------------------------------------------------------------- -spec start_service(service_name(), [service_opt()]) -> ok @@ -80,9 +110,9 @@ start_service(SvcName, Opts) when is_list(Opts) -> diameter_config:start_service(SvcName, Opts). -%%% -------------------------------------------------------------------------- -%%% stop_service/1 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% stop_service/1 +%% --------------------------------------------------------------------------- -spec stop_service(service_name()) -> ok @@ -91,9 +121,9 @@ start_service(SvcName, Opts) stop_service(SvcName) -> diameter_config:stop_service(SvcName). -%%% -------------------------------------------------------------------------- -%%% services/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% services/0 +%% --------------------------------------------------------------------------- -spec services() -> [service_name()]. @@ -101,9 +131,9 @@ stop_service(SvcName) -> services() -> [Name || {Name, _} <- diameter_service:services()]. -%%% -------------------------------------------------------------------------- -%%% service_info/2 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% service_info/2 +%% --------------------------------------------------------------------------- -spec service_info(service_name(), atom() | [atom()]) -> any(). @@ -111,9 +141,9 @@ services() -> service_info(SvcName, Option) -> diameter_service:info(SvcName, Option). -%%% -------------------------------------------------------------------------- -%%% add_transport/3 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% add_transport/3 +%% --------------------------------------------------------------------------- -spec add_transport(service_name(), {listen|connect, [transport_opt()]}) -> {ok, transport_ref()} @@ -123,9 +153,9 @@ add_transport(SvcName, {T, Opts} = Cfg) when is_list(Opts), (T == connect orelse T == listen) -> diameter_config:add_transport(SvcName, Cfg). -%%% -------------------------------------------------------------------------- -%%% remove_transport/2 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% remove_transport/2 +%% --------------------------------------------------------------------------- -spec remove_transport(service_name(), transport_pred()) -> ok | {error, term()}. @@ -133,12 +163,9 @@ add_transport(SvcName, {T, Opts} = Cfg) remove_transport(SvcName, Pred) -> diameter_config:remove_transport(SvcName, Pred). -%%% -------------------------------------------------------------------------- -%%% # subscribe(SvcName) -%%% -%%% Description: Subscribe to #diameter_event{} messages for the specified -%%% service. -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% subscribe/1 +%% --------------------------------------------------------------------------- -spec subscribe(service_name()) -> true. @@ -146,9 +173,9 @@ remove_transport(SvcName, Pred) -> subscribe(SvcName) -> diameter_service:subscribe(SvcName). -%%% -------------------------------------------------------------------------- -%%% # unsubscribe(SvcName) -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% unsubscribe/1 +%% --------------------------------------------------------------------------- -spec unsubscribe(service_name()) -> true. @@ -156,9 +183,9 @@ subscribe(SvcName) -> unsubscribe(SvcName) -> diameter_service:unsubscribe(SvcName). -%%% ---------------------------------------------------------- -%%% # session_id/1 -%%% ---------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% session_id/1 +%% --------------------------------------------------------------------------- -spec session_id('DiameterIdentity'()) -> 'OctetString'(). @@ -166,9 +193,9 @@ unsubscribe(SvcName) -> session_id(Ident) -> diameter_session:session_id(Ident). -%%% ---------------------------------------------------------- -%%% # origin_state_id/0 -%%% ---------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% origin_state_id/0 +%% --------------------------------------------------------------------------- -spec origin_state_id() -> 'Unsigned32'(). @@ -176,9 +203,9 @@ session_id(Ident) -> origin_state_id() -> diameter_session:origin_state_id(). -%%% -------------------------------------------------------------------------- -%%% # call/[34] -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% call/3,4 +%% --------------------------------------------------------------------------- -spec call(service_name(), app_alias(), any(), [call_opt()]) -> any(). @@ -188,3 +215,125 @@ call(SvcName, App, Message, Options) -> call(SvcName, App, Message) -> call(SvcName, App, Message, []). + +%% =========================================================================== + +%% Diameter basic types + +-type 'OctetString'() :: iolist(). +-type 'Integer32'() :: -2147483647..2147483647. +-type 'Integer64'() :: -9223372036854775807..9223372036854775807. +-type 'Unsigned32'() :: 0..4294967295. +-type 'Unsigned64'() :: 0..18446744073709551615. +-type 'Float32'() :: '-infinity' | float() | infinity. +-type 'Float64'() :: '-infinity' | float() | infinity. +-type 'Grouped'() :: list() | tuple(). + +%% Diameter derived types + +-type 'Address'() + :: inet:ip_address() + | string(). + +-type 'Time'() :: {{integer(), 1..12, 1..31}, + {0..23, 0..59, 0..59}}. +-type 'UTF8String'() :: iolist(). +-type 'DiameterIdentity'() :: 'OctetString'(). +-type 'DiameterURI'() :: 'OctetString'(). +-type 'Enumerated'() :: 'Integer32'(). +-type 'IPFilterRule'() :: 'OctetString'(). +-type 'QoSFilterRule'() :: 'OctetString'(). + +%% The handle to a service. + +-type service_name() + :: any(). + +%% Capabilities options/avps on start_service/2 and/or add_transport/2 + +-type capability() + :: {'Origin-Host', 'DiameterIdentity'()} + | {'Origin-Realm', 'DiameterIdentity'()} + | {'Host-IP-Address', ['Address'()]} + | {'Vendor-Id', 'Unsigned32'()} + | {'Product-Name', 'UTF8String'()} + | {'Supported-Vendor-Id', ['Unsigned32'()]} + | {'Auth-Application-Id', ['Unsigned32'()]} + | {'Vendor-Specific-Application-Id', ['Grouped'()]} + | {'Firmware-Revision', 'Unsigned32'()}. + +%% Filters for call/4 + +-type peer_filter() + :: none + | host + | realm + | {host, any|'DiameterIdentity'()} + | {realm, any|'DiameterIdentity'()} + | {eval, evaluable()} + | {neg, peer_filter()} + | {all, [peer_filter()]} + | {any, [peer_filter()]}. + +-type evaluable() + :: {module(), atom(), list()} + | fun() + | maybe_improper_list(evaluable(), list()). + +%% Options passed to start_service/2 + +-type service_opt() + :: capability() + | {application, [application_opt()]}. + +-type application_opt() + :: {alias, app_alias()} + | {dictionary, module()} + | {module, app_module()} + | {state, any()} + | {call_mutates_state, boolean()} + | {answer_errors, callback|report|discard}. + +-type app_alias() + :: any(). + +-type app_module() + :: module() + | maybe_improper_list(module(), list()) + | #diameter_callback{}. + +%% Identifier returned by add_transport/2 + +-type transport_ref() + :: reference(). + +%% Options passed to add_transport/2 + +-type transport_opt() + :: {transport_module, atom()} + | {transport_config, any()} + | {applications, [app_alias()]} + | {capabilities, [capability()]} + | {capabilities_cb, evaluable()} + | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} + | {reconnect_timer, 'Unsigned32'()} + | {private, any()}. + +%% Predicate passed to remove_transport/2 + +-type transport_pred() + :: fun((reference(), connect|listen, list()) -> boolean()) + | fun((reference(), list()) -> boolean()) + | fun((list()) -> boolean()) + | reference() + | list() + | {connect|listen, transport_pred()} + | {atom(), atom(), list()}. + +%% Options passed to call/4 + +-type call_opt() + :: {extra, list()} + | {filter, peer_filter()} + | {timeout, 'Unsigned32'()} + | detach. diff --git a/lib/diameter/src/base/diameter_callback.erl b/lib/diameter/src/base/diameter_callback.erl index 6d5c8cdca1..90431099b0 100644 --- a/lib/diameter/src/base/diameter_callback.erl +++ b/lib/diameter/src/base/diameter_callback.erl @@ -18,11 +18,58 @@ %% %% -%% A minimal application callback module. +%% A diameter callback module that can redirect selected callbacks, +%% providing reasonable default implementations otherwise. +%% +%% To order alternate callbacks, configure a #diameter_callback record +%% as the Diameter application callback in question. The record has +%% one field for each callback function as well as 'default' and +%% 'extra' fields. A function-specific field can be set to a +%% diameter:evaluable() in order to redirect the callback +%% corresponding to that field, or to 'false' to request the default +%% callback implemented in this module. If neither of these fields are +%% set then the 'default' field determines the form of the callback: a +%% module name results in the usual callback as if the module had been +%% configured directly as the callback module, a diameter_evaluable() +%% in a callback applied to the atom-valued callback name and argument +%% list. For all callbacks not to this module, the 'extra' field is a +%% list of additional arguments, following arguments supplied by +%% diameter but preceeding those of the diameter:evaluable() being +%% applied. +%% +%% For example, the following config to diameter:start_service/2, in +%% an 'application' tuple, would result in only a mymod:peer_down/3 +%% callback, this module implementing the remaining callbacks. +%% +%% {module, #diameter_callback{peer_down = {mymod, down, []}}} +%% +%% Equivalently, this can also be specified with a [Mod | Args] +%% field/value list as follows. +%% +%% {module, [diameter_callback, {peer_down, {mymod, down, []}}]} +%% +%% The following would result in this module suppying peer_up and +%% peer_down callback, others taking place in module mymod. +%% +%% {module, #diameter_callback{peer_up = false, +%% peer_down = false, +%% default = mymod}} +%% +%% The following would result in all callbacks taking place as +%% calls to mymod:diameter/2. +%% +%% {module, #diameter_callback{default = {mymod, diameter, []}}} +%% +%% The following are equivalent and result in all callbacks being +%% provided by this module. +%% +%% {module, #diameter_callback{}} +%% {module, diameter_callback} %% -module(diameter_callback). +%% Default callbacks when no aleternate is specified. -export([peer_up/3, peer_down/3, pick_peer/4, @@ -32,6 +79,16 @@ handle_answer/4, handle_error/4]). +%% Callbacks taking a #diameter_callback record. +-export([peer_up/4, + peer_down/4, + pick_peer/5, + prepare_request/4, + prepare_retransmit/4, + handle_request/4, + handle_answer/5, + handle_error/5]). + -include_lib("diameter/include/diameter.hrl"). %%% ---------------------------------------------------------- @@ -41,51 +98,137 @@ peer_up(_Svc, _Peer, State) -> State. +peer_up(Svc, Peer, State, D) -> + cb(peer_up, + [Svc, Peer, State], + D#diameter_callback.peer_up, + D). + %%% ---------------------------------------------------------- %%% # peer_down/3 %%% ---------------------------------------------------------- -peer_down(_SvcName, _Peer, State) -> +peer_down(_Svc, _Peer, State) -> State. +peer_down(Svc, Peer, State, D) -> + cb(peer_down, + [Svc, Peer, State], + D#diameter_callback.peer_down, + D). + %%% ---------------------------------------------------------- %%% # pick_peer/4 %%% ---------------------------------------------------------- -pick_peer([Peer|_], _, _SvcName, _State) -> - {ok, Peer}. +pick_peer([Peer|_], _, _Svc, _State) -> + {ok, Peer}; +pick_peer([], _, _Svc, _State) -> + false. + +pick_peer(PeersL, PeersR, Svc, State, D) -> + cb(pick_peer, + [PeersL, PeersR, Svc, State], + D#diameter_callback.pick_peer, + D). %%% ---------------------------------------------------------- %%% # prepare_request/3 %%% ---------------------------------------------------------- -prepare_request(Pkt, _SvcName, _Peer) -> +prepare_request(Pkt, _Svc, _Peer) -> {send, Pkt}. +prepare_request(Pkt, Svc, Peer, D) -> + cb(prepare_request, + [Pkt, Svc, Peer], + D#diameter_callback.prepare_request, + D). + %%% ---------------------------------------------------------- %%% # prepare_retransmit/3 %%% ---------------------------------------------------------- -prepare_retransmit(Pkt, _SvcName, _Peer) -> +prepare_retransmit(Pkt, _Svc, _Peer) -> {send, Pkt}. +prepare_retransmit(Pkt, Svc, Peer, D) -> + cb(prepare_retransmit, + [Pkt, Svc, Peer], + D#diameter_callback.prepare_retransmit, + D). + %%% ---------------------------------------------------------- %%% # handle_request/3 %%% ---------------------------------------------------------- -handle_request(_Pkt, _SvcName, _Peer) -> +handle_request(_Pkt, _Svc, _Peer) -> {protocol_error, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED +handle_request(Pkt, Svc, Peer, D) -> + cb(handle_request, + [Pkt, Svc, Peer], + D#diameter_callback.handle_request, + D). + %%% ---------------------------------------------------------- %%% # handle_answer/4 %%% ---------------------------------------------------------- -handle_answer(#diameter_packet{msg = Ans}, _Req, _SvcName, _Peer) -> - Ans. +handle_answer(#diameter_packet{msg = Ans, errors = []}, _Req, _Svc, _Peer) -> + Ans; +handle_answer(#diameter_packet{msg = Ans, errors = Es}, _Req, _Svc, _Peer) -> + [Ans | Es]. + +handle_answer(Pkt, Req, Svc, Peer, D) -> + cb(handle_answer, + [Pkt, Req, Svc, Peer], + D#diameter_callback.handle_answer, + D). %%% --------------------------------------------------------------------------- %%% # handle_error/4 %%% --------------------------------------------------------------------------- -handle_error(Reason, _Req, _SvcName, _Peer) -> +handle_error(Reason, _Req, _Svc, _Peer) -> {error, Reason}. + +handle_error(Reason, Req, Svc, Peer, D) -> + cb(handle_error, + [Reason, Req, Svc, Peer], + D#diameter_callback.handle_error, + D). + +%% =========================================================================== + +%% cb/4 + +%% Unspecified callback: use default field to determine something +%% appropriate. +cb(CB, Args, undefined, D) -> + cb(CB, Args, D); + +%% Explicitly requested default. +cb(CB, Args, false, _) -> + apply(?MODULE, CB, Args); + +%% A specified callback. +cb(_, Args, F, #diameter_callback{extra = X}) -> + diameter_lib:eval([[F|X] | Args]). + +%% cb/3 + +%% No user-supplied default: call ours. +cb(CB, Args, #diameter_callback{default = undefined}) -> + apply(?MODULE, CB, Args); + +%% Default is a module name: make the usual callback. +cb(CB, Args, #diameter_callback{default = M, + extra = X}) + when is_atom(M) -> + apply(M, CB, Args ++ X); + +%% Default is something else: apply if to callback name and arguments. +cb(CB, Args, #diameter_callback{default = F, + extra = X}) -> + diameter_lib:eval([F, CB, Args | X]). diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 842a9e6103..6c4d60ee9b 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -54,7 +54,6 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_types.hrl"). -include("diameter_gen_base_rfc3588.hrl"). -define(SUCCESS, 2001). %% DIAMETER_SUCCESS @@ -75,13 +74,17 @@ build_CER(Caps) -> try_it([fun bCER/1, Caps]). -spec recv_CER(#diameter_base_CER{}, #diameter_service{}) - -> tried({['Unsigned32'()], #diameter_caps{}, #diameter_base_CEA{}}). + -> tried({[diameter:'Unsigned32'()], + #diameter_caps{}, + #diameter_base_CEA{}}). recv_CER(CER, Svc) -> try_it([fun rCER/2, CER, Svc]). -spec recv_CEA(#diameter_base_CEA{}, #diameter_service{}) - -> tried({['Unsigned32'()], ['Unsigned32'()], #diameter_caps{}}). + -> tried({[diameter:'Unsigned32'()], + [diameter:'Unsigned32'()], + #diameter_caps{}}). recv_CEA(CEA, Svc) -> try_it([fun rCEA/2, CEA, Svc]). diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index a6b48fe65b..9253af0de2 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -605,6 +605,13 @@ app_acc({application, Opts}, Acc) -> app_acc(_, Acc) -> Acc. +init_mod(#diameter_callback{} = R) -> + init_mod([diameter_callback, R]); +init_mod([diameter_callback, #diameter_callback{}] = L) -> + L; +init_mod([diameter_callback = M | L]) + when is_list(L) -> + [M, init_cb(L)]; init_mod(M) when is_atom(M) -> [M]; @@ -614,6 +621,14 @@ init_mod([M|_] = L) init_mod(M) -> ?THROW({module, M}). +init_cb(List) -> + Fields = record_info(fields, diameter_callback), + Defaults = lists:zip(Fields, tl(tuple_to_list(#diameter_callback{}))), + Values = [V || F <- Fields, + D <- [proplists:get_value(F, Defaults)], + V <- [proplists:get_value(F, List, D)]], + #diameter_callback{} = list_to_tuple([diameter_callback | Values]). + init_mutable(M) when M == true; M == false -> diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index fae5d763dc..99644814d2 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -46,7 +46,6 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_types.hrl"). -include("diameter_gen_base_rfc3588.hrl"). -define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU'). @@ -78,7 +77,8 @@ parent :: pid(), transport :: pid(), service :: #diameter_service{}, - dpr = false :: false | {'Unsigned32'(), 'Unsigned32'()}}). + dpr = false :: false | {diameter:'Unsigned32'(), + diameter:'Unsigned32'()}}). %% | hop by hop and end to end identifiers %% There are non-3588 states possible as a consequence of 5.6.1 of the diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 7adcf1c265..3dfdcee2b2 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -64,7 +64,6 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --include("diameter_types.hrl"). -define(STATE_UP, up). -define(STATE_DOWN, down). @@ -117,7 +116,7 @@ {pid :: match(pid()), type :: match(connect | accept), ref :: match(reference()), %% key into diameter_config - options :: match([transport_opt()]), %% as passed to start_transport + options :: match([diameter:transport_opt()]),%% from start_transport op_state = ?STATE_DOWN :: match(?STATE_DOWN | ?STATE_UP), started = now(), %% at process start conn = false :: match(boolean() | pid())}). @@ -126,7 +125,7 @@ %% Record representing a peer_fsm process. -record(conn, {pid :: pid(), - apps :: [{0..16#FFFFFFFF, app_alias()}], %% {Id, Alias} + apps :: [{0..16#FFFFFFFF, diameter:app_alias()}], %% {Id, Alias} caps :: #diameter_caps{}, started = now(), %% at process start peer :: pid()}). %% key into peerT @@ -137,16 +136,16 @@ handler :: match(pid()), %% request process transport :: match(pid()), %% peer process caps :: match(#diameter_caps{}), - app :: match(app_alias()), %% #diameter_app.alias + app :: match(diameter:app_alias()), %% #diameter_app.alias dictionary :: match(module()), %% #diameter_app.dictionary - module :: match(nonempty_improper_list(module(), list())), + module :: match([module() | list()]), %% #diameter_app.module - filter :: match(peer_filter()), + filter :: match(diameter:peer_filter()), packet :: match(#diameter_packet{})}). %% Record call/4 options are parsed into. -record(options, - {filter = none :: peer_filter(), + {filter = none :: diameter:peer_filter(), extra = [] :: list(), timeout = ?DEFAULT_TIMEOUT :: 0..16#FFFFFFFF, detach = false :: boolean()}). @@ -630,10 +629,6 @@ insert(Tbl, Rec) -> ets:insert(Tbl, Rec), Rec. -monitor(Pid) -> - erlang:monitor(process, Pid), - Pid. - %% Using the process dictionary for the callback state was initially %% just a way to make what was horrendous trace (big state record and %% much else everywhere) somewhat more readable. There's not as much @@ -647,11 +642,6 @@ mod_state(Alias) -> mod_state(Alias, ModS) -> put({?MODULE, mod_state, Alias}, ModS). -%% have_transport/2 - -have_transport(SvcName, Ref) -> - [] /= diameter_config:have_transport(SvcName, Ref). - %%% --------------------------------------------------------------------------- %%% # shutdown/2 %%% --------------------------------------------------------------------------- @@ -820,10 +810,10 @@ start(Ref, Type, Opts, #state{peerT = PeerT, service = Svc}) when Type == connect; Type == accept -> - Pid = monitor(s(Type, Ref, {ConnT, - Opts, - SvcName, - merge_service(Opts, Svc)})), + Pid = s(Type, Ref, {ConnT, + Opts, + SvcName, + merge_service(Opts, Svc)}), insert(PeerT, #peer{pid = Pid, type = Type, ref = Ref, @@ -836,7 +826,13 @@ start(Ref, Type, Opts, #state{peerT = PeerT, %% callbacks. s(Type, Ref, T) -> - diameter_watchdog:start({Type, Ref}, T). + case diameter_watchdog:start({Type, Ref}, T) of + {_MRef, Pid} -> + Pid; + Pid when is_pid(Pid) -> %% from old code + erlang:monitor(process, Pid), + Pid + end. %% merge_service/2 @@ -1131,7 +1127,7 @@ start_tc(Tc, T, _) -> %% tc_timeout/2 tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) -> - tc(have_transport(SvcName, Ref), T, S). + tc(diameter_config:have_transport(SvcName, Ref), T, S). tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} = S) -> @@ -1159,7 +1155,7 @@ close(Pid, #state{service_name = SvcName, options = Opts} = fetch(PeerT, Pid), - c(Pid, have_transport(SvcName, Ref), Opts). + c(Pid, diameter_config:have_transport(SvcName, Ref), Opts). %% Tell watchdog to (maybe) die later ... c(Pid, true, Opts) -> @@ -1490,7 +1486,7 @@ pd([], _) -> send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, Timeout) when node() == node(TPid) -> %% Store the outgoing request before sending to avoid a race with - %% reply reception. + %% reply reception. TRef = store_request(TPid, Bin, Req, Timeout), send(TPid, Pkt), TRef; @@ -1946,7 +1942,7 @@ reply(Msg, Dict, TPid, #diameter_packet{errors = Es, when [] == Es; is_record(hd(Msg), diameter_header) -> Pkt = diameter_codec:encode(Dict, make_answer_packet(Msg, ReqPkt)), - incr(send, Pkt, Dict, TPid), %% count result codes in sent answers + incr(send, Pkt, TPid), %% count result codes in sent answers send(TPid, Pkt#diameter_packet{transport_data = TD}); %% Or not: set Result-Code and Failed-AVP AVP's. @@ -2218,12 +2214,11 @@ a(#diameter_packet{errors = []} SvcName, AE, #request{transport = TPid, - dictionary = Dict, caps = Caps, packet = P} = Req) -> try - incr(in, Pkt, Dict, TPid) + incr(in, Pkt, TPid) of _ -> cb(Req, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]) @@ -2254,18 +2249,17 @@ e(Pkt, SvcName, discard, Req) -> %% Increment a stats counter for an incoming or outgoing message. %% TODO: fix -incr(_, #diameter_packet{msg = undefined}, _, _) -> +incr(_, #diameter_packet{msg = undefined}, _) -> ok; -incr(Dir, Pkt, Dict, TPid) +incr(Dir, Pkt, TPid) when is_pid(TPid) -> #diameter_packet{header = #diameter_header{is_error = E} = Hdr, msg = Rec} = Pkt, - D = choose(E, ?BASE, Dict), - RC = int(get_avp_value(D, 'Result-Code', Rec)), + RC = int(get_avp_value(?BASE, 'Result-Code', Rec)), PE = is_protocol_error(RC), %% Check that the E bit is set only for 3xxx result codes. @@ -2273,7 +2267,7 @@ incr(Dir, Pkt, Dict, TPid) orelse (E andalso PE) orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]), - Ctr = rc_counter(D, Rec, RC), + Ctr = rc_counter(Rec, RC), is_tuple(Ctr) andalso incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}). @@ -2291,11 +2285,11 @@ incr(TPid, Counter) -> %% Maintain statistics assuming one or the other, not both, which is %% surely the intent of the RFC. -rc_counter(_, _, RC) +rc_counter(_, RC) when is_integer(RC) -> {'Result-Code', RC}; -rc_counter(D, Rec, _) -> - rcc(get_avp_value(D, 'Experimental-Result', Rec)). +rc_counter(Rec, _) -> + rcc(get_avp_value(?BASE, 'Experimental-Result', Rec)). %% 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 @@ -2355,8 +2349,8 @@ rt(#request{packet = #diameter_packet{msg = undefined}}, _) -> false; %% TODO: Not what we should do. %% ... or not. -rt(#request{packet = #diameter_packet{msg = Msg}, dictionary = D} = Req, S) -> - find_transport(get_destination(Msg, D), Req, S). +rt(#request{packet = #diameter_packet{msg = Msg}} = Req, S) -> + find_transport(get_destination(Msg), Req, S). %%% --------------------------------------------------------------------------- %%% # report_status/5 @@ -2468,12 +2462,12 @@ find_transport({alias, Alias}, Msg, Opts, #state{service = Svc} = S) -> find_transport(#diameter_app{} = App, Msg, Opts, S) -> ft(App, Msg, Opts, S). -ft(#diameter_app{module = Mod, dictionary = D} = App, Msg, Opts, S) -> +ft(#diameter_app{module = Mod} = App, Msg, Opts, S) -> #options{filter = Filter, extra = Xtra} = Opts, pick_peer(App#diameter_app{module = Mod ++ Xtra}, - get_destination(Msg, D), + get_destination(Msg), Filter, S); ft(false = No, _, _, _) -> @@ -2509,11 +2503,11 @@ find_transport([_,_] = RH, Filter, S). -%% get_destination/2 +%% get_destination/1 -get_destination(Msg, Dict) -> - [str(get_avp_value(Dict, 'Destination-Realm', Msg)), - str(get_avp_value(Dict, 'Destination-Host', Msg))]. +get_destination(Msg) -> + [str(get_avp_value(?BASE, 'Destination-Realm', Msg)), + str(get_avp_value(?BASE, 'Destination-Host', Msg))]. %% This is not entirely correct. The avp could have an arity 1, in %% which case an empty list is a DiameterIdentity of length 0 rather diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl index bb91e97f39..4c468f207c 100644 --- a/lib/diameter/src/base/diameter_session.erl +++ b/lib/diameter/src/base/diameter_session.erl @@ -26,8 +26,6 @@ %% towards diameter_sup -export([init/0]). --include("diameter_types.hrl"). - -define(INT64, 16#FFFFFFFFFFFFFFFF). -define(INT32, 16#FFFFFFFF). @@ -73,7 +71,7 @@ %% consumed (see Section 6.2) SHOULD be silently discarded. -spec sequence() - -> 'Unsigned32'(). + -> diameter:'Unsigned32'(). sequence() -> Instr = {_Pos = 2, _Incr = 1, _Threshold = ?INT32, _SetVal = 0}, @@ -97,7 +95,7 @@ sequence() -> %% counter retained in non-volatile memory across restarts. -spec origin_state_id() - -> 'Unsigned32'(). + -> diameter:'Unsigned32'(). origin_state_id() -> ets:lookup_element(diameter_sequence, origin_state_id, 2). @@ -130,8 +128,8 @@ origin_state_id() -> %% <optional value> is implementation specific but may include a modem's %% device Id, a layer 2 address, timestamp, etc. --spec session_id('DiameterIdentity'()) - -> 'OctetString'(). +-spec session_id(diameter:'DiameterIdentity'()) + -> diameter:'OctetString'(). %% Note that Session-Id has type UTF8String and that any OctetString %% is a UTF8String. diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl index 6b1b1b8d39..9ae289034c 100644 --- a/lib/diameter/src/base/diameter_types.erl +++ b/lib/diameter/src/base/diameter_types.erl @@ -42,8 +42,23 @@ 'IPFilterRule'/2, 'QoSFilterRule'/2]). +%% Functions taking the AVP name in question as second parameter. +-export(['OctetString'/3, + 'Integer32'/3, + 'Integer64'/3, + 'Unsigned32'/3, + 'Unsigned64'/3, + 'Float32'/3, + 'Float64'/3, + 'Address'/3, + 'Time'/3, + 'UTF8String'/3, + 'DiameterIdentity'/3, + 'DiameterURI'/3, + 'IPFilterRule'/3, + 'QoSFilterRule'/3]). + -include_lib("diameter/include/diameter.hrl"). --include("diameter_internal.hrl"). -define(UINT(N,X), ((0 =< X) andalso (X < 1 bsl N))). -define(SINT(N,X), ((-1*(1 bsl (N-1)) < X) andalso (X < 1 bsl (N-1)))). @@ -433,6 +448,50 @@ uenc([C | Rest], Acc) -> 'Time'(encode, zero) -> <<0:32>>. +%% ------------------------------------------------------------------------- + +'OctetString'(M, _, Data) -> + 'OctetString'(M, Data). + +'Integer32'(M, _, Data) -> + 'Integer32'(M, Data). + +'Integer64'(M, _, Data) -> + 'Integer64'(M, Data). + +'Unsigned32'(M, _, Data) -> + 'Unsigned32'(M, Data). + +'Unsigned64'(M, _, Data) -> + 'Unsigned64'(M, Data). + +'Float32'(M, _, Data) -> + 'Float32'(M, Data). + +'Float64'(M, _, Data) -> + 'Float64'(M, Data). + +'Address'(M, _, Data) -> + 'Address'(M, Data). + +'Time'(M, _, Data) -> + 'Time'(M, Data). + +'UTF8String'(M, _, Data) -> + 'UTF8String'(M, Data). + +'DiameterIdentity'(M, _, Data) -> + 'DiameterIdentity'(M, Data). + +'DiameterURI'(M, _, Data) -> + 'DiameterURI'(M, Data). + +'IPFilterRule'(M, _, Data) -> + 'IPFilterRule'(M, Data). + +'QoSFilterRule'(M, _, Data) -> + 'QoSFilterRule'(M, Data). + %% =========================================================================== %% =========================================================================== diff --git a/lib/diameter/src/base/diameter_types.hrl b/lib/diameter/src/base/diameter_types.hrl deleted file mode 100644 index 02bf8a74dd..0000000000 --- a/lib/diameter/src/base/diameter_types.hrl +++ /dev/null @@ -1,139 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2011. 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% -%% - -%% -%% Types for function specifications, primarily in diameter.erl. This -%% has nothing specifically to do with diameter_types.erl. -%% - --type evaluable() - :: {module(), atom(), list()} - | fun() - | nonempty_improper_list(evaluable(), list()). %% [evaluable() | Args] - --type app_alias() - :: any(). - --type service_name() - :: any(). - -%% Diameter basic types - --type 'OctetString'() :: iolist(). --type 'Integer32'() :: -2147483647..2147483647. --type 'Integer64'() :: -9223372036854775807..9223372036854775807. --type 'Unsigned32'() :: 0..4294967295. --type 'Unsigned64'() :: 0..18446744073709551615. --type 'Float32'() :: '-infinity' | float() | infinity. --type 'Float64'() :: '-infinity' | float() | infinity. --type 'Grouped'() :: list() | tuple(). - -%% Diameter derived types - --type 'Address'() - :: inet:ip_address() - | string(). - --type 'Time'() :: {{integer(), 1..12, 1..31}, - {0..23, 0..59, 0..59}}. --type 'UTF8String'() :: iolist(). --type 'DiameterIdentity'() :: 'OctetString'(). --type 'DiameterURI'() :: 'OctetString'(). --type 'Enumerated'() :: 'Integer32'(). --type 'IPFilterRule'() :: 'OctetString'(). --type 'QoSFilterRule'() :: 'OctetString'(). - -%% Capabilities options/avps on start_service/2 and/or add_transport/2 - --type capability() - :: {'Origin-Host', 'DiameterIdentity'()} - | {'Origin-Realm', 'DiameterIdentity'()} - | {'Host-IP-Address', ['Address'()]} - | {'Vendor-Id', 'Unsigned32'()} - | {'Product-Name', 'UTF8String'()} - | {'Supported-Vendor-Id', ['Unsigned32'()]} - | {'Auth-Application-Id', ['Unsigned32'()]} - | {'Vendor-Specific-Application-Id', ['Grouped'()]} - | {'Firmware-Revision', 'Unsigned32'()}. - -%% Filters for call/4 - --type peer_filter() - :: none - | host - | realm - | {host, any|'DiameterIdentity'()} - | {realm, any|'DiameterIdentity'()} - | {eval, evaluable()} - | {neg, peer_filter()} - | {all, [peer_filter()]} - | {any, [peer_filter()]}. - -%% Options passed to start_service/2 - --type service_opt() - :: capability() - | {application, [application_opt()]}. - --type application_opt() - :: {alias, app_alias()} - | {dictionary, module()} - | {module, app_module()} - | {state, any()} - | {call_mutates_state, boolean()} - | {answer_errors, callback|report|discard}. - --type app_module() - :: module() - | nonempty_improper_list(module(), list()). %% list with module() head - -%% Identifier returned by add_transport/2 - --type transport_ref() - :: reference(). - -%% Options passed to add_transport/2 - --type transport_opt() - :: {transport_module, atom()} - | {transport_config, any()} - | {applications, [app_alias()]} - | {capabilities, [capability()]} - | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} - | {reconnect_timer, 'Unsigned32'()} - | {private, any()}. - -%% Predicate passed to remove_transport/2 - --type transport_pred() - :: fun((reference(), connect|listen, list()) -> boolean()) - | fun((reference(), list()) -> boolean()) - | fun((list()) -> boolean()) - | reference() - | list() - | {connect|listen, transport_pred()} - | {atom(), atom(), list()}. - -%% Options passed to call/4 - --type call_opt() - :: {extra, list()} - | {filter, peer_filter()} - | {timeout, 'Unsigned32'()} - | detach. diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 6dc53d9f31..fb22fd8275 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -59,10 +59,19 @@ message_data}). %% term passed into diameter_service with message %% start/2 +%% +%% Start a monitor before the watchdog is allowed to proceed to ensure +%% that a failed capabilities exchange produces the desired exit +%% reason. start({_,_} = Type, T) -> - {ok, Pid} = diameter_watchdog_sup:start_child({Type, self(), T}), - Pid. + Ref = make_ref(), + {ok, Pid} = diameter_watchdog_sup:start_child({Ref, {Type, self(), T}}), + try + {erlang:monitor(process, Pid), Pid} + after + Pid ! Ref + end. start_link(T) -> {ok, _} = proc_lib:start_link(?MODULE, @@ -80,14 +89,29 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({T, Pid, {ConnT, Opts, SvcName, #diameter_service{applications = Apps, - capabilities = Caps} - = Svc}}) -> - {M,S,U} = now(), - random:seed(M,S,U), +i({Ref, {_, Pid, _} = T}) -> + MRef = erlang:monitor(process, Pid), + receive + Ref -> + make_state(T); + {'DOWN', MRef, process, _, _} = D -> + exit({shutdown, D}) + end; + +i({_, Pid, _} = T) -> %% from old code + erlang:monitor(process, Pid), + make_state(T). + +make_state({T, Pid, {ConnT, + Opts, + SvcName, + #diameter_service{applications = Apps, + capabilities = Caps} + = Svc}}) -> + random:seed(now()), putr(restart, {T, Opts, Svc}), %% save seeing it in trace putr(dwr, dwr(Caps)), %% - #watchdog{parent = monitor(Pid), + #watchdog{parent = Pid, transport = monitor(diameter_peer_fsm:start(T, Opts, Svc)), tw = proplists:get_value(watchdog_timer, Opts, diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 0fd4a0b301..1e31c40afe 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -20,17 +20,18 @@ -module(diameter_codegen). %% -%% This module generates .erl and .hrl files for encode/decode -%% modules from the orddict parsed from a .dia (aka spec) file by -%% dis_spec_util. The generated code is very simple (one-liners), the -%% generated functions being called by code included from dis_gen.hrl -%% in order to encode/decode messages and AVPs. The orddict itself is -%% returned by dict/0 in the generated module and dis_spec_util calls -%% this function when importing spec files. (That is, beam has to be -%% compiled from an imported spec file before it can be imported.) +%% This module generates erl/hrl files for encode/decode modules +%% from the orddict parsed from a dictionary file (.dia) by +%% diameter_dict_util. The generated code is simple (one-liners), +%% the generated functions being called by code included iin the +%% generated modules from diameter_gen.hrl. The orddict itself is +%% returned by dict/0 in the generated module and diameter_dict_util +%% calls this function when importing dictionaries as a consequence +%% of @inherits sections. That is, @inherits introduces a dependency +%% on the beam file of another dictionary. %% --export([from_spec/4]). +-export([from_dict/4]). %% Internal exports (for test). -export([file/1, @@ -38,17 +39,23 @@ file/3]). -include("diameter_forms.hrl"). +-include("diameter_vsn.hrl"). -%% Generated functions that could have no generated clauses will have -%% a trailing ?UNEXPECTED clause that should never execute. --define(UNEXPECTED(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)], - [], - [?APPLY(erlang, - error, - [?TERM({unexpected, getr(module)})])]}). +-define(S, atom_to_list). +-define(A, list_to_atom). +-define(Atom(T), ?ATOM(?A(T))). -from_spec(File, Spec, Opts, Mode) -> +%% =========================================================================== + +-spec from_dict(File, Spec, Opts, Mode) + -> ok + when File :: string(), + Spec :: orddict:orddict(), + Opts :: list(), + Mode :: spec | erl | hrl. + +from_dict(File, Spec, Opts, Mode) -> Outdir = proplists:get_value(outdir, Opts, "."), putr(verbose, lists:member(verbose, Opts)), putr(debug, lists:member(debug, Opts)), @@ -73,7 +80,7 @@ getr(Key) -> %% =========================================================================== %% =========================================================================== -%% Generate from parsed spec in a file. +%% Generate from parsed dictionary in a file. file(F) -> file(F, spec). @@ -83,55 +90,46 @@ file(F, Mode) -> file(F, Outdir, Mode) -> {ok, [Spec]} = file:consult(F), - from_spec(F, Spec, Outdir, Mode). + from_dict(F, Spec, Outdir, Mode). %% =========================================================================== %% =========================================================================== -choose(true, X, _) -> X; -choose(false, _, X) -> X. - get_value(Key, Plist) -> proplists:get_value(Key, Plist, []). -write(Path, [C|_] = Spec) - when is_integer(C) -> - w(Path, Spec, "~s"); -write(Path, Spec) -> - w(Path, Spec, "~p."). +write(Path, Str) -> + w(Path, Str, "~s"). -w(Path, Spec, Fmt) -> +write_term(Path, T) -> + w(Path, T, "~p."). + +w(Path, T, Fmt) -> {ok, Fd} = file:open(Path, [write]), - io:fwrite(Fd, Fmt ++ "~n", [Spec]), + io:fwrite(Fd, Fmt ++ "~n", [T]), file:close(Fd). codegen(File, Spec, Outdir, Mode) -> Mod = mod(File, orddict:find(name, Spec)), Path = filename:join(Outdir, Mod), %% minus extension - gen(Mode, Spec, Mod, Path), + gen(Mode, Spec, ?A(Mod), Path), ok. mod(File, error) -> filename:rootname(filename:basename(File)); mod(_, {ok, Mod}) -> - atom_to_list(Mod). + Mod. gen(spec, Spec, _Mod, Path) -> - write(Path ++ ".spec", Spec); + write_term(Path ++ ".spec", [?VERSION | Spec]); gen(hrl, Spec, Mod, Path) -> gen_hrl(Path ++ ".hrl", Mod, Spec); -gen(erl = Mode, Spec, Mod, Path) - when is_list(Mod) -> - gen(Mode, Spec, list_to_atom(Mod), Path); - gen(erl, Spec, Mod, Path) -> - putr(module, Mod), %% used by ?UNEXPECTED. - Forms = [{?attribute, module, Mod}, {?attribute, compile, [{parse_transform, diameter_exprecs}]}, - {?attribute, compile, [nowarn_unused_function]}, + {?attribute, compile, [{parse_transform, diameter_nowarn}]}, {?attribute, export, [{name, 0}, {id, 0}, {vendor_id, 0}, @@ -175,7 +173,7 @@ gen(erl, Spec, Mod, Path) -> gen_erl(Path, insert_hrl_forms(Spec, Forms)). gen_erl(Path, Forms) -> - getr(debug) andalso write(Path ++ ".forms", Forms), + getr(debug) andalso write_term(Path ++ ".forms", Forms), write(Path ++ ".erl", header() ++ erl_prettypr:format(erl_syntax:form_list(Forms))). @@ -224,16 +222,16 @@ a_record(Prefix, ProjF, L) -> lists:map(fun(T) -> a_record(ProjF(T), Prefix) end, L). a_record({Nm, Avps}, Prefix) -> - Name = list_to_atom(Prefix ++ atom_to_list(Nm)), + Name = list_to_atom(Prefix ++ Nm), Fields = lists:map(fun field/1, Avps), {?attribute, record, {Name, Fields}}. field(Avp) -> {Name, Arity} = avp_info(Avp), if 1 == Arity -> - {?record_field, ?ATOM(Name)}; + {?record_field, ?Atom(Name)}; true -> - {?record_field, ?ATOM(Name), ?NIL} + {?record_field, ?Atom(Name), ?NIL} end. %%% ------------------------------------------------------------------------ @@ -256,25 +254,33 @@ c_id({ok, Id}) -> {?clause, [], [], [?INTEGER(Id)]}; c_id(error) -> - ?UNEXPECTED(0). + ?BADARG(0). %%% ------------------------------------------------------------------------ %%% # vendor_id/0 %%% ------------------------------------------------------------------------ f_vendor_id(Spec) -> - {Id, _} = orddict:fetch(vendor, Spec), {?function, vendor_id, 0, - [{?clause, [], [], [?INTEGER(Id)]}]}. + [{?clause, [], [], [b_vendor_id(orddict:find(vendor, Spec))]}]}. + +b_vendor_id({ok, {Id, _}}) -> + ?INTEGER(Id); +b_vendor_id(error) -> + ?APPLY(erlang, error, [?TERM(undefined)]). %%% ------------------------------------------------------------------------ %%% # vendor_name/0 %%% ------------------------------------------------------------------------ f_vendor_name(Spec) -> - {_, Name} = orddict:fetch(vendor, Spec), {?function, vendor_name, 0, - [{?clause, [], [], [?ATOM(Name)]}]}. + [{?clause, [], [], [b_vendor_name(orddict:find(vendor, Spec))]}]}. + +b_vendor_name({ok, {_, Name}}) -> + ?Atom(Name); +b_vendor_name(error) -> + ?APPLY(erlang, error, [?TERM(undefined)]). %%% ------------------------------------------------------------------------ %%% # msg_name/1 @@ -287,22 +293,18 @@ f_msg_name(Spec) -> %% DIAMETER_COMMAND_UNSUPPORTED should be replied. msg_name(Spec) -> - lists:flatmap(fun c_msg_name/1, - proplists:get_value(command_codes, Spec, [])) + lists:flatmap(fun c_msg_name/1, proplists:get_value(command_codes, + Spec, + [])) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('')]}]. c_msg_name({Code, Req, Ans}) -> [{?clause, [?INTEGER(Code), ?ATOM(true)], [], - [?ATOM(mname(Req))]}, + [?Atom(Req)]}, {?clause, [?INTEGER(Code), ?ATOM(false)], [], - [?ATOM(mname(Ans))]}]. - -mname({N, _Abbr}) -> - N; -mname(N) -> - N. + [?Atom(Ans)]}]. %%% ------------------------------------------------------------------------ %%% # msg2rec/1 @@ -313,30 +315,11 @@ f_msg2rec(Spec) -> msg2rec(Spec) -> Pre = prefix(Spec), - Dict = dict:from_list(lists:flatmap(fun msgs/1, - get_value(command_codes, Spec))), - lists:flatmap(fun(T) -> msg2rec(T, Dict, Pre) end, - get_value(messages, Spec)) - ++ [?UNEXPECTED(1)]. - -msgs({_Code, Req, Ans}) -> - [{mname(Req), Req}, {mname(Ans), Ans}]. - -msg2rec({N,_,_,_,_}, Dict, Pre) -> - c_msg2rec(fetch_names(N, Dict), Pre). - -fetch_names(Name, Dict) -> - case dict:find(Name, Dict) of - {ok, N} -> - N; - error -> - Name - end. + lists:map(fun(T) -> c_msg2rec(T, Pre) end, get_value(messages, Spec)) + ++ [?BADARG(1)]. -c_msg2rec({N,A}, Pre) -> - [c_name2rec(N, N, Pre), c_name2rec(A, N, Pre)]; -c_msg2rec(N, Pre) -> - [c_name2rec(N, N, Pre)]. +c_msg2rec({N,_,_,_,_}, Pre) -> + c_name2rec(N, Pre). %%% ------------------------------------------------------------------------ %%% # rec2msg/1 @@ -348,10 +331,10 @@ f_rec2msg(Spec) -> rec2msg(Spec) -> Pre = prefix(Spec), lists:map(fun(T) -> c_rec2msg(T, Pre) end, get_value(messages, Spec)) - ++ [?UNEXPECTED(1)]. + ++ [?BADARG(1)]. c_rec2msg({N,_,_,_,_}, Pre) -> - {?clause, [?ATOM(rec_name(N, Pre))], [], [?ATOM(N)]}. + {?clause, [?Atom(rec_name(N, Pre))], [], [?Atom(N)]}. %%% ------------------------------------------------------------------------ %%% # name2rec/1 @@ -364,11 +347,11 @@ name2rec(Spec) -> Pre = prefix(Spec), Groups = get_value(grouped, Spec) ++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)), - lists:map(fun({N,_,_,_}) -> c_name2rec(N, N, Pre) end, Groups) + lists:map(fun({N,_,_,_}) -> c_name2rec(N, Pre) end, Groups) ++ [{?clause, [?VAR('T')], [], [?CALL(msg2rec, [?VAR('T')])]}]. -c_name2rec(Name, Rname, Pre) -> - {?clause, [?ATOM(Name)], [], [?ATOM(rec_name(Rname, Pre))]}. +c_name2rec(Name, Pre) -> + {?clause, [?Atom(Name)], [], [?Atom(rec_name(Name, Pre))]}. avps({_Mod, Avps}) -> Avps. @@ -390,32 +373,47 @@ f_avp_name(Spec) -> %% allocated by IANA (see Section 11.1). avp_name(Spec) -> - Avps = get_value(avp_types, Spec) - ++ lists:flatmap(fun avps/1, get_value(import_avps, Spec)), - {Vid, _} = orddict:fetch(vendor, Spec), - Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, - get_value(avp_vendor_id, Spec)), + Avps = get_value(avp_types, Spec), + Imported = get_value(import_avps, Spec), + Vid = orddict:find(vendor, Spec), + Vs = vendor_id_map(Spec), - lists:map(fun(T) -> c_avp_name(T, Vid, Vs) end, Avps) + lists:map(fun(T) -> c_avp_name(T, Vs, Vid) end, Avps) + ++ lists:flatmap(fun(T) -> c_imported_avp_name(T, Vs) end, Imported) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('AVP')]}]. -c_avp_name({Name, Code, Type, Flags, _Encr}, Vid, Vs) -> - c_avp_name({Name, Type}, - Code, - lists:member('V', Flags), - Vid, - proplists:get_value(Name, Vs)). +c_avp_name({Name, Code, Type, Flags}, Vs, Vid) -> + c_avp_name_(?TERM({?A(Name), ?A(Type)}), + ?INTEGER(Code), + vid(Name, Flags, Vs, Vid)). -c_avp_name(T, Code, false, _, undefined = U) -> - {?clause, [?INTEGER(Code), ?ATOM(U)], +%% Note that an imported AVP's vendor id is determined by +%% avp_vendor_id in the inheriting module and vendor in the inherited +%% module. In particular, avp_vendor_id in the inherited module is +%% ignored so can't just call Mod:avp_header/1 to retrieve the vendor +%% id. A vendor id specified in @grouped is equivalent to one +%% specified as avp_vendor_id. + +c_imported_avp_name({Mod, Avps}, Vs) -> + lists:map(fun(A) -> c_avp_name(A, Vs, {module, Mod}) end, Avps). + +c_avp_name_(T, Code, undefined = U) -> + {?clause, [Code, ?ATOM(U)], [], - [?TERM(T)]}; + [T]}; -c_avp_name(T, Code, true, Vid, V) - when is_integer(Vid) -> - {?clause, [?INTEGER(Code), ?INTEGER(choose(V == undefined, Vid, V))], +c_avp_name_(T, Code, Vid) -> + {?clause, [Code, ?INTEGER(Vid)], [], - [?TERM(T)]}. + [T]}. + +vendor_id_map(Spec) -> + lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, + get_value(avp_vendor_id, Spec)) + ++ lists:flatmap(fun({_,_,[],_}) -> []; + ({N,_,[V],_}) -> [{N,V}] + end, + get_value(grouped, Spec)). %%% ------------------------------------------------------------------------ %%% # avp_arity/2 @@ -445,60 +443,75 @@ c_avp_arity(Name, Avps) -> c_arity(Name, Avp) -> {AvpName, Arity} = avp_info(Avp), - {?clause, [?ATOM(Name), ?ATOM(AvpName)], [], [?TERM(Arity)]}. + {?clause, [?Atom(Name), ?Atom(AvpName)], [], [?TERM(Arity)]}. %%% ------------------------------------------------------------------------ %%% # avp/3 %%% ------------------------------------------------------------------------ f_avp(Spec) -> - {?function, avp, 3, avp(Spec) ++ [?UNEXPECTED(3)]}. + {?function, avp, 3, avp(Spec) ++ [?BADARG(3)]}. avp(Spec) -> - Native = get_value(avp_types, Spec), - Custom = get_value(custom_types, Spec), - Imported = get_value(import_avps, Spec), - Enums = get_value(enums, Spec), - avp([{N,T} || {N,_,T,_,_} <- Native], Imported, Custom, Enums). + Native = get_value(avp_types, Spec), + CustomMods = get_value(custom_types, Spec), + TypeMods = get_value(codecs, Spec), + Imported = get_value(import_avps, Spec), + Enums = get_value(enum, Spec), -avp(Native, Imported, Custom, Enums) -> - Dict = orddict:from_list(Native), + Custom = lists:map(fun({M,As}) -> {M, custom_types, As} end, + CustomMods) + ++ lists:map(fun({M,As}) -> {M, codecs, As} end, + TypeMods), + avp(types(Native), Imported, Custom, Enums). + +types(Avps) -> + lists:map(fun({N,_,T,_}) -> {N,T} end, Avps). - report(native, Dict), +avp(Native, Imported, Custom, Enums) -> + report(native, Native), report(imported, Imported), report(custom, Custom), - CustomNames = lists:flatmap(fun({_,Ns}) -> Ns end, Custom), + TypeDict = lists:foldl(fun({N,_,T,_}, D) -> orddict:store(N,T,D) end, + orddict:from_list(Native), + lists:flatmap(fun avps/1, Imported)), + + CustomNames = lists:flatmap(fun({_,_,Ns}) -> Ns end, Custom), lists:map(fun c_base_avp/1, - lists:filter(fun({N,_}) -> - false == lists:member(N, CustomNames) - end, + lists:filter(fun({N,_}) -> not_in(CustomNames, N) end, Native)) - ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums) end, Imported) - ++ lists:flatmap(fun(C) -> cs_custom_avp(C, Dict) end, Custom). + ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums, CustomNames) end, + Imported) + ++ lists:flatmap(fun(C) -> cs_custom_avp(C, TypeDict) end, Custom). + +not_in(List, X) -> + not lists:member(X, List). c_base_avp({AvpName, T}) -> - {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], - [base_avp(AvpName, T)]}. + [b_base_avp(AvpName, T)]}. -base_avp(AvpName, 'Enumerated') -> - ?CALL(enumerated_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]); +b_base_avp(AvpName, "Enumerated") -> + ?CALL(enumerated_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); -base_avp(AvpName, 'Grouped') -> - ?CALL(grouped_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]); +b_base_avp(AvpName, "Grouped") -> + ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); -base_avp(_, Type) -> - ?APPLY(diameter_types, Type, [?VAR('T'), ?VAR('Data')]). +b_base_avp(_, Type) -> + ?APPLY(diameter_types, ?A(Type), [?VAR('T'), ?VAR('Data')]). -cs_imported_avp({Mod, Avps}, Enums) -> - lists:map(fun(A) -> imported_avp(Mod, A, Enums) end, Avps). +cs_imported_avp({Mod, Avps}, Enums, CustomNames) -> + lists:map(fun(A) -> imported_avp(Mod, A, Enums) end, + lists:filter(fun({N,_,_,_}) -> not_in(CustomNames, N) end, + Avps)). -imported_avp(_Mod, {AvpName, _, 'Grouped' = T, _, _}, _) -> +imported_avp(_Mod, {AvpName, _, "Grouped" = T, _}, _) -> c_base_avp({AvpName, T}); -imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) -> +imported_avp(Mod, {AvpName, _, "Enumerated" = T, _}, Enums) -> case lists:keymember(AvpName, 1, Enums) of true -> c_base_avp({AvpName, T}); @@ -506,34 +519,40 @@ imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) -> c_imported_avp(Mod, AvpName) end; -imported_avp(Mod, {AvpName, _, _, _, _}, _) -> +imported_avp(Mod, {AvpName, _, _, _}, _) -> c_imported_avp(Mod, AvpName). c_imported_avp(Mod, AvpName) -> - {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], [?APPLY(Mod, avp, [?VAR('T'), ?VAR('Data'), - ?ATOM(AvpName)])]}. + ?Atom(AvpName)])]}. -cs_custom_avp({Mod, Avps}, Dict) -> - lists:map(fun(N) -> c_custom_avp(Mod, N, orddict:fetch(N, Dict)) end, +cs_custom_avp({Mod, Key, Avps}, Dict) -> + lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end, Avps). -c_custom_avp(Mod, AvpName, Type) -> - {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], +c_custom_avp(Mod, Key, AvpName, Type) -> + {F,A} = custom(Key, AvpName, Type), + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], - [?APPLY(Mod, AvpName, [?VAR('T'), ?ATOM(Type), ?VAR('Data')])]}. + [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), ?VAR('Data')])]}. + +custom(custom_types, AvpName, Type) -> + {AvpName, Type}; +custom(codecs, AvpName, Type) -> + {Type, AvpName}. %%% ------------------------------------------------------------------------ %%% # enumerated_avp/3 %%% ------------------------------------------------------------------------ f_enumerated_avp(Spec) -> - {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?UNEXPECTED(3)]}. + {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?BADARG(3)]}. enumerated_avp(Spec) -> - Enums = get_value(enums, Spec), + Enums = get_value(enum, Spec), lists:flatmap(fun cs_enumerated_avp/1, Enums) ++ lists:flatmap(fun({M,Es}) -> enumerated_avp(M, Es, Enums) end, get_value(import_enums, Spec)). @@ -554,11 +573,11 @@ cs_enumerated_avp(false, _, _) -> cs_enumerated_avp({AvpName, Values}) -> lists:flatmap(fun(V) -> c_enumerated_avp(AvpName, V) end, Values). -c_enumerated_avp(AvpName, {I,_}) -> - [{?clause, [?ATOM(decode), ?ATOM(AvpName), ?TERM(<<I:32/integer>>)], +c_enumerated_avp(AvpName, {_,I}) -> + [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32/integer>>)], [], [?TERM(I)]}, - {?clause, [?ATOM(encode), ?ATOM(AvpName), ?INTEGER(I)], + {?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)], [], [?TERM(<<I:32/integer>>)]}]. @@ -567,7 +586,7 @@ c_enumerated_avp(AvpName, {I,_}) -> %%% ------------------------------------------------------------------------ f_msg_header(Spec) -> - {?function, msg_header, 1, msg_header(Spec) ++ [?UNEXPECTED(1)]}. + {?function, msg_header, 1, msg_header(Spec) ++ [?BADARG(1)]}. msg_header(Spec) -> msg_header(get_value(messages, Spec), Spec). @@ -582,7 +601,7 @@ msg_header(Msgs, Spec) -> %% Note that any application id in the message header spec is ignored. c_msg_header(Name, Code, Flags, ApplId) -> - {?clause, [?ATOM(Name)], + {?clause, [?Atom(Name)], [], [?TERM({Code, encode_msg_flags(Flags), ApplId})]}. @@ -598,50 +617,61 @@ emf('ERR', N) -> N bor 2#00100000. %%% ------------------------------------------------------------------------ f_avp_header(Spec) -> - {?function, avp_header, 1, avp_header(Spec) ++ [?UNEXPECTED(1)]}. + {?function, avp_header, 1, avp_header(Spec) ++ [?BADARG(1)]}. avp_header(Spec) -> Native = get_value(avp_types, Spec), Imported = get_value(import_avps, Spec), - {Vid, _} = orddict:fetch(vendor, Spec), - Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, - get_value(avp_vendor_id, Spec)), + Vid = orddict:find(vendor, Spec), + Vs = vendor_id_map(Spec), - lists:flatmap(fun(A) -> c_avp_header({Vid, Vs}, A) end, + lists:flatmap(fun(A) -> c_avp_header(A, Vs, Vid) end, Native ++ Imported). -c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags, _Encr}) -> - [{?clause, [?ATOM(Name)], +c_avp_header({Name, Code, _Type, Flags}, Vs, Vid) -> + [{?clause, [?Atom(Name)], [], [?TERM({Code, encode_avp_flags(Flags), vid(Name, Flags, Vs, Vid)})]}]; -c_avp_header({_, Vs}, {Mod, Avps}) -> - lists:map(fun(A) -> c_avp_header(Vs, Mod, A) end, Avps). +c_avp_header({Mod, Avps}, Vs, _Vid) -> + lists:map(fun(A) -> c_imported_avp_header(A, Mod, Vs) end, Avps). -c_avp_header(Vs, Mod, {Name, _, _, Flags, _}) -> - Apply = ?APPLY(Mod, avp_header, [?ATOM(Name)]), - {?clause, [?ATOM(Name)], +%% Note that avp_vendor_id in the inherited dictionary is ignored. The +%% value must be changed in the inheriting dictionary. This is +%% consistent with the semantics of avp_name/2. + +c_imported_avp_header({Name, _Code, _Type, _Flags}, Mod, Vs) -> + Apply = ?APPLY(Mod, avp_header, [?Atom(Name)]), + {?clause, [?Atom(Name)], [], [case proplists:get_value(Name, Vs) of undefined -> Apply; Vid -> - true = lists:member('V', Flags), %% sanity check ?CALL(setelement, [?INTEGER(3), Apply, ?INTEGER(Vid)]) end]}. encode_avp_flags(Fs) -> lists:foldl(fun eaf/2, 0, Fs). -eaf('V', F) -> 2#10000000 bor F; -eaf('M', F) -> 2#01000000 bor F; -eaf('P', F) -> 2#00100000 bor F. +eaf($V, F) -> 2#10000000 bor F; +eaf($M, F) -> 2#01000000 bor F; +eaf($P, F) -> 2#00100000 bor F. vid(Name, Flags, Vs, Vid) -> - v(lists:member('V', Flags), Name, Vs, Vid). + v(lists:member($V, Flags), Name, Vs, Vid). + +v(true = T, Name, Vs, {module, Mod}) -> + v(T, Name, Vs, {ok, {Mod:vendor_id(), Mod:vendor_name()}}); v(true, Name, Vs, Vid) -> - proplists:get_value(Name, Vs, Vid); + case proplists:get_value(Name, Vs) of + undefined -> + {ok, {Id, _}} = Vid, + Id; + Id -> + Id + end; v(false, _, _, _) -> undefined. @@ -656,19 +686,19 @@ empty_value(Spec) -> Imported = lists:flatmap(fun avps/1, get_value(import_enums, Spec)), Groups = get_value(grouped, Spec) ++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)), - Enums = [T || {N,_} = T <- get_value(enums, Spec), + Enums = [T || {N,_} = T <- get_value(enum, Spec), not lists:keymember(N, 1, Imported)] ++ Imported, lists:map(fun c_empty_value/1, Groups ++ Enums) ++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}]. c_empty_value({Name, _, _, _}) -> - {?clause, [?ATOM(Name)], + {?clause, [?Atom(Name)], [], - [?CALL(empty_group, [?ATOM(Name)])]}; + [?CALL(empty_group, [?Atom(Name)])]}; c_empty_value({Name, _}) -> - {?clause, [?ATOM(Name)], + {?clause, [?Atom(Name)], [], [?TERM(<<0:32/integer>>)]}. @@ -678,7 +708,7 @@ c_empty_value({Name, _}) -> f_dict(Spec) -> {?function, dict, 0, - [{?clause, [], [], [?TERM(Spec)]}]}. + [{?clause, [], [], [?TERM([?VERSION | Spec])]}]}. %%% ------------------------------------------------------------------------ %%% # gen_hrl/3 @@ -706,10 +736,10 @@ gen_hrl(Path, Mod, Spec) -> write("ENUM Macros", Fd, - m_enums(PREFIX, false, get_value(enums, Spec))), + m_enums(PREFIX, false, get_value(enum, Spec))), write("DEFINE Macros", Fd, - m_enums(PREFIX, false, get_value(defines, Spec))), + m_enums(PREFIX, false, get_value(define, Spec))), lists:foreach(fun({M,Es}) -> write("ENUM Macros from " ++ atom_to_list(M), @@ -751,8 +781,8 @@ m_enums(Prefix, Wrap, Enums) -> m_enum(Prefix, B, {Name, Values}) -> P = Prefix ++ to_upper(Name) ++ "_", - lists:map(fun({I,A}) -> - N = ["'", P, to_upper(z(atom_to_list(A))), "'"], + lists:map(fun({A,I}) -> + N = ["'", P, to_upper(z(A)), "'"], wrap(B, N, ["-define(", N, ", ", integer_to_list(I), ").\n"]) @@ -794,34 +824,34 @@ header() -> "%%\n\n"). hrl_header(Name) -> - header() ++ "-hrl_name('" ++ Name ++ ".hrl').\n". + header() ++ "-hrl_name('" ++ ?S(Name) ++ ".hrl').\n". %% avp_info/1 avp_info(Entry) -> %% {Name, Arity} case Entry of - {'<',A,'>'} -> {A, 1}; - {A} -> {A, 1}; - [A] -> {A, {0,1}}; + {{A}} -> {A, 1}; + {A} -> {A, 1}; + [A] -> {A, {0,1}}; {Q,T} -> {A,_} = avp_info(T), - {A, arity(Q)} + {A, arity(T,Q)} end. %% Normalize arity to 1 or {N,X} where N is an integer. A record field %% for an AVP is list-valued iff the normalized arity is not 1. -arity('*' = Inf) -> {0, Inf}; -arity({'*', N}) -> {0, N}; -arity({1,1}) -> 1; -arity(T) -> T. +arity({{_}}, '*' = Inf) -> {0, Inf}; +arity([_], '*' = Inf) -> {0, Inf}; +arity({_}, '*' = Inf) -> {1, Inf}; +arity(_, {_,_} = Q) -> Q. prefix(Spec) -> case orddict:find(prefix, Spec) of {ok, P} -> - atom_to_list(P) ++ "_"; + P ++ "_"; error -> "" end. rec_name(Name, Prefix) -> - list_to_atom(Prefix ++ atom_to_list(Name)). + Prefix ++ Name. diff --git a/lib/diameter/src/compiler/diameter_dict_parser.yrl b/lib/diameter/src/compiler/diameter_dict_parser.yrl new file mode 100644 index 0000000000..6fd4cedd23 --- /dev/null +++ b/lib/diameter/src/compiler/diameter_dict_parser.yrl @@ -0,0 +1,324 @@ +%% -*- erlang -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% A grammar for dictionary specification. +%% + +Nonterminals + application_id avp avp_code avp_def avp_defs avp_flags avp_header + avp_header_tok avp_name avp_names avp_ref avp_spec avp_type + avp_vendor avps bit bits command_def command_id diameter_name + dictionary enum_def enum_defs group_def group_defs header header_tok + ident idents message_defs module qual section sections. + +Terminals + avp_types avp_vendor_id codecs custom_types define enum grouped + id inherits messages name prefix vendor + number word + '{' '}' '<' '>' '[' ']' '*' '::=' ':' ',' '-' + code + 'answer-message' + 'AVP' 'AVP-Header' + 'Diameter' 'Diameter-Header' 'Header' + 'REQ' 'PXY' 'ERR'. + +Rootsymbol dictionary. + +Endsymbol '$end'. + +%% =========================================================================== + +dictionary -> sections : '$1'. + +sections -> '$empty' : []. +sections -> section sections : ['$1' | '$2']. + +section -> name ident : ['$1', '$2']. +section -> prefix ident : ['$1', '$2']. +section -> id number : ['$1', '$2']. +section -> vendor number ident : ['$1', '$2', '$3']. +section -> inherits module avp_names : ['$1', '$2' | '$3']. +section -> avp_types avp_defs : ['$1' | '$2']. +section -> avp_vendor_id number avp_names : ['$1', '$2' | '$3']. +section -> custom_types module avp_names : ['$1', '$2' | '$3']. +section -> codecs module avp_names : ['$1', '$2' | '$3']. +section -> messages message_defs : ['$1' | '$2']. +section -> grouped group_defs : ['$1' | '$2']. +section -> enum ident enum_defs : ['$1', '$2' | '$3']. +section -> define ident enum_defs : ['$1', '$2' | '$3']. + +%% ===================================== + +module -> ident : '$1'. + +avp_names -> idents : '$1'. %% Note: not 'AVP' + +avp_defs -> '$empty' : []. +avp_defs -> avp_def avp_defs : ['$1' | '$2']. + +avp_def -> ident number avp_type avp_flags : ['$1', '$2', '$3', '$4']. + +avp_type -> ident : '$1'. + +idents -> '$empty' : []. +idents -> ident idents : ['$1' | '$2']. + +avp_flags -> '-' : + {_, Lineno} = '$1', + {word, Lineno, ""}. +avp_flags -> ident : + '$1'. +%% Could support lowercase here if there's a use for distinguishing +%% between Must and Should in the future in deciding whether or not +%% to set a flag. + +ident -> word : '$1'. + +%% Don't bother mapping reserved words to make these usable in this +%% context. That an AVP can't be named Diameter-Header is probably no +%% great loss, and that it can't be named AVP may even save someone +%% from themselves. (Temporarily at least.) + +group_defs -> '$empty' : []. +group_defs -> group_def group_defs : ['$1' | '$2']. + +message_defs -> '$empty' : []. +message_defs -> command_def message_defs : ['$1' | '$2']. + +enum_defs -> '$empty' : []. +enum_defs -> enum_def enum_defs : ['$1' | '$2']. + +enum_def -> ident number : ['$1', '$2']. + +%% ===================================== +%% 3.2. Command Code ABNF specification +%% +%% Every Command Code defined MUST include a corresponding ABNF +%% specification, which is used to define the AVPs that MUST or MAY be +%% present when sending the message. The following format is used in +%% the definition: + +%% command-def = <command-name> "::=" diameter-message +%% +%% command-name = diameter-name +%% +%% diameter-name = ALPHA *(ALPHA / DIGIT / "-") +%% +%% diameter-message = header [ *fixed] [ *required] [ *optional] + +%% answer-message is a special case. +command_def -> 'answer-message' '::=' '<' header_tok ':' code + ',' 'ERR' '[' 'PXY' ']' '>' + avps + : ['$1', false | '$13']. + +command_def -> diameter_name '::=' header avps + : ['$1', '$3' | '$4']. +%% Ensure the order fixed/required/optional by semantic checks rather +%% than grammatically since the latter requires more lookahead: don't +%% know until after a leading qual which of the three it is that's +%% being parsed. + +diameter_name -> ident : '$1'. + +%% header = "<" "Diameter Header:" command-id +%% [r-bit] [p-bit] [e-bit] [application-id] ">" +%% +%% command-id = 1*DIGIT +%% ; The Command Code assigned to the command +%% +%% r-bit = ", REQ" +%% ; If present, the 'R' bit in the Command +%% ; Flags is set, indicating that the message +%% ; is a request, as opposed to an answer. +%% +%% p-bit = ", PXY" +%% ; If present, the 'P' bit in the Command +%% ; Flags is set, indicating that the message +%% ; is proxiable. +%% +%% e-bit = ", ERR" +%% ; If present, the 'E' bit in the Command +%% ; Flags is set, indicating that the answer +%% ; message contains a Result-Code AVP in +%% ; the "protocol error" class. +%% +%% application-id = 1*DIGIT + +header -> '<' header_tok ':' command_id bits application_id '>' + : ['$4', '$5', '$6']. + +command_id -> number : '$1'. + +%% Accept both the form of the base definition and the typo (fixed in +%% 3588bis) of the grammar. +header_tok -> 'Diameter' 'Header'. +header_tok -> 'Diameter-Header'. + +bits -> '$empty' : []. +bits -> ',' bit bits : ['$2' | '$3']. + +%% ERR only makes sense for answer-message so don't allow it here +%% (despite 3588). +bit -> 'REQ' : '$1'. +bit -> 'PXY' : '$1'. + +application_id -> '$empty' : false. +application_id -> number : '$1'. + +%% fixed = [qual] "<" avp-spec ">" +%% ; Defines the fixed position of an AVP +%% +%% required = [qual] "{" avp-spec "}" +%% ; The AVP MUST be present and can appear +%% ; anywhere in the message. +%% +%% optional = [qual] "[" avp-name "]" +%% ; The avp-name in the 'optional' rule cannot +%% ; evaluate to any AVP Name which is included +%% ; in a fixed or required rule. The AVP can +%% ; appear anywhere in the message. +%% ; +%% ; NOTE: "[" and "]" have a slightly different +%% ; meaning than in ABNF (RFC 5234]). These braces +%% ; cannot be used to express optional fixed rules +%% ; (such as an optional ICV at the end). To do this, +%% ; the convention is '0*1fixed'. + +avps -> '$empty' : []. +avps -> avp avps : ['$1' | '$2']. + +avp -> avp_ref : [false | '$1']. +avp -> qual avp_ref : ['$1' | '$2']. + +avp_ref -> '<' avp_spec '>' : [$<, '$2']. +avp_ref -> '{' avp_name '}' : [${, '$2']. +avp_ref -> '[' avp_name ']' : [$[, '$2']. +%% Note that required can be an avp_name, not just avp_spec. 'AVP' +%% is specified as required by Failed-AVP for example. + +%% qual = [min] "*" [max] +%% ; See ABNF conventions, RFC 5234 Section 4. +%% ; The absence of any qualifiers depends on +%% ; whether it precedes a fixed, required, or +%% ; optional rule. If a fixed or required rule has +%% ; no qualifier, then exactly one such AVP MUST +%% ; be present. If an optional rule has no +%% ; qualifier, then 0 or 1 such AVP may be +%% ; present. If an optional rule has a qualifier, +%% ; then the value of min MUST be 0 if present. +%% +%% min = 1*DIGIT +%% ; The minimum number of times the element may +%% ; be present. If absent, the default value is zero +%% ; for fixed and optional rules and one for required +%% ; rules. The value MUST be at least one for for +%% ; required rules. +%% +%% max = 1*DIGIT +%% ; The maximum number of times the element may +%% ; be present. If absent, the default value is +%% ; infinity. A value of zero implies the AVP MUST +%% ; NOT be present. + +qual -> number '*' number : {'$1', '$3'}. +qual -> number '*' : {'$1', true}. +qual -> '*' number : {true, '$2'}. +qual -> '*' : true. + +%% avp-spec = diameter-name +%% ; The avp-spec has to be an AVP Name, defined +%% ; in the base or extended Diameter +%% ; specifications. + +avp_spec -> diameter_name : '$1'. + +%% avp-name = avp-spec / "AVP" +%% ; The string "AVP" stands for *any* arbitrary AVP +%% ; Name, not otherwise listed in that command code +%% ; definition. Addition this AVP is recommended for +%% ; all command ABNFs to allow for extensibility. + +avp_name -> 'AVP' : '$1'. +avp_name -> avp_spec : '$1'. + +%% The following is a definition of a fictitious command code: +%% +%% Example-Request ::= < Diameter Header: 9999999, REQ, PXY > +%% { User-Name } +%% * { Origin-Host } +%% * [ AVP ] + +%% ===================================== +%% 4.4. Grouped AVP Values +%% +%% The Diameter protocol allows AVP values of type 'Grouped'. This +%% implies that the Data field is actually a sequence of AVPs. It is +%% possible to include an AVP with a Grouped type within a Grouped type, +%% that is, to nest them. AVPs within an AVP of type Grouped have the +%% same padding requirements as non-Grouped AVPs, as defined in Section +%% 4. +%% +%% The AVP Code numbering space of all AVPs included in a Grouped AVP is +%% the same as for non-grouped AVPs. 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. +%% +%% Every Grouped AVP defined MUST include a corresponding grammar, using +%% ABNF [RFC5234] (with modifications), as defined below. + +%% grouped-avp-def = <name> "::=" avp +%% +%% name-fmt = ALPHA *(ALPHA / DIGIT / "-") +%% +%% name = name-fmt +%% ; The name has to be the name of an AVP, +%% ; defined in the base or extended Diameter +%% ; specifications. +%% +%% avp = header [ *fixed] [ *required] [ *optional] + +group_def -> ident '::=' avp_header avps : ['$1', '$3' | '$4']. + +%% header = "<" "AVP-Header:" avpcode [vendor] ">" +%% +%% avpcode = 1*DIGIT +%% ; The AVP Code assigned to the Grouped AVP +%% +%% vendor = 1*DIGIT +%% ; The Vendor-ID assigned to the Grouped AVP. +%% ; If absent, the default value of zero is +%% ; used. + +avp_header -> '<' avp_header_tok ':' avp_code avp_vendor '>' + : ['$4', '$5']. + +avp_header_tok -> 'AVP-Header'. +avp_header_tok -> 'AVP' 'Header'. + +avp_code -> number : '$1'. + +avp_vendor -> '$empty' : false. +avp_vendor -> number : '$1'. diff --git a/lib/diameter/src/compiler/diameter_dict_scanner.erl b/lib/diameter/src/compiler/diameter_dict_scanner.erl new file mode 100644 index 0000000000..45189376fb --- /dev/null +++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl @@ -0,0 +1,276 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(diameter_dict_scanner). + +%% +%% A scanner for dictionary files of the form expected by yecc. +%% + +-export([scan/1, + format_error/1]). + +-export([is_name/1]). + +%% ----------------------------------------------------------- +%% # scan/1 +%% ----------------------------------------------------------- + +-spec scan(string() | binary()) + -> {ok, [Token]} + | {error, {string(), string(), Lineno}} + when Token :: {word, Lineno, string()} + | {number, Lineno, non_neg_integer()} + | {Symbol, Lineno}, + Lineno :: pos_integer(), + Symbol :: '{' | '}' | '<' | '>' | '[' | ']' + | '*' | '::=' | ':' | ',' | '-' + | avp_types + | avp_vendor_id + | codecs + | custom_types + | define + | grouped + | id + | inherits + | messages + | name + | prefix + | vendor + | '$end' + | code + | 'answer-message' + | 'AVP' + | 'AVP-Header' + | 'Diameter' + | 'Diameter-Header' + | 'Header' + | 'REQ' + | 'PXY' + | 'ERR'. + +scan(B) + when is_binary(B) -> + scan(binary_to_list(B)); +scan(S) -> + scan(S, {1, []}). + +scan(S, {Lineno, Acc}) -> + case split(S) of + '$end' = E -> + {ok, lists:reverse([{E, Lineno} | Acc])}; + {Tok, Rest} -> + scan(Rest, acc(Tok, Lineno, Acc)); + Reason when is_list(Reason) -> + {error, {Reason, S, Lineno}} + end. + +%% format_error/1 + +format_error({Reason, Input, Lineno}) -> + io_lib:format("~s at line ~p: ~s", + [Reason, Lineno, head(Input, [], 20, true)]). + +%% is_name/1 + +is_name([H|T]) -> + is_alphanum(H) andalso lists:all(fun is_name_ch/1, T). + +%% =========================================================================== + +head(Str, Acc, N, _) + when [] == Str; + 0 == N; + $\r == hd(Str); + $\n == hd(Str) -> + lists:reverse(Acc); +head([C|Rest], Acc, N, true = T) %% skip leading whitespace + when C == $\s; + C == $\t; + C == $\f; + C == $\v -> + head(Rest, Acc, N, T); +head([C|Rest], Acc, N, _) -> + head(Rest, [C|Acc], N-1, false). + +acc(endline, Lineno, Acc) -> + {Lineno + 1, Acc}; +acc(T, Lineno, Acc) -> + {Lineno, [tok(T, Lineno) | Acc]}. + +tok({Cat, Sym}, Lineno) -> + {Cat, Lineno, Sym}; +tok(Sym, Lineno) -> + {Sym, Lineno}. + +%% # split/1 +%% +%% Output: {Token, Rest} | atom() + +%% Finito. +split("") -> + '$end'; + +%% Skip comments. This precludes using semicolon for any other purpose. +split([$;|T]) -> + split(lists:dropwhile(fun(C) -> not is_eol_ch(C) end, T)); + +%% Beginning of a section. +split([$@|T]) -> + {Name, Rest} = lists:splitwith(fun is_name_ch/1, T), + case section(Name) of + false -> + "Unknown section"; + 'end' -> + '$end'; + A -> + {A, Rest} + end; + +split("::=" ++ T) -> + {'::=', T}; + +split([H|T]) + when H == ${; H == $}; + H == $<; H == $>; + H == $[; H == $]; + H == $*; H == $:; H == $,; H == $- -> + {list_to_atom([H]), T}; + +%% RFC 3588 requires various names to begin with a letter but 3GPP (for +%% one) abuses this. (eg 3GPP-Charging-Id in TS32.299.) +split([H|_] = L) when $0 =< H, H =< $9 -> + {P, Rest} = splitwith(fun is_name_ch/1, L), + Tok = try + {number, read_int(P)} + catch + error:_ -> + word(P) + end, + {Tok, Rest}; + +split([H|_] = L) when $a =< H, H =< $z; + $A =< H, H =< $Z -> + {P, Rest} = splitwith(fun is_name_ch/1, L), + {word(P), Rest}; + +split([$'|T]) -> + case lists:splitwith(fun(C) -> not lists:member(C, "'\r\n") end, T) of + {[_|_] = A, [$'|Rest]} -> + {{word, A}, Rest}; + {[], [$'|_]} -> + "Empty string"; + _ -> %% not terminated on same line + "Unterminated string" + end; + +%% Line ending of various forms. +split([$\r,$\n|T]) -> + {endline, T}; +split([C|T]) + when C == $\r; + C == $\n -> + {endline, T}; + +%% Ignore whitespace. +split([C|T]) + when C == $\s; + C == $\t; + C == $\f; + C == $\v -> + split(T); + +split(_) -> + "Unexpected character". + +%% word/1 + +%% Reserved words significant in parsing ... +word(S) + when S == "answer-message"; + S == "code"; + S == "AVP"; + S == "AVP-Header"; + S == "Diameter"; + S == "Diameter-Header"; + S == "Header"; + S == "REQ"; + S == "PXY"; + S == "ERR" -> + list_to_atom(S); + +%% ... or not. +word(S) -> + {word, S}. + +%% section/1 + +section(N) + when N == "avp_types"; + N == "avp_vendor_id"; + N == "codecs"; + N == "custom_types"; + N == "define"; + N == "end"; + N == "enum"; + N == "grouped"; + N == "id"; + N == "inherits"; + N == "messages"; + N == "name"; + N == "prefix"; + N == "vendor" -> + list_to_atom(N); +section(_) -> + false. + +%% read_int/1 + +read_int([$0,X|S]) + when X == $X; + X == $x -> + {ok, [N], []} = io_lib:fread("~16u", S), + N; + +read_int(S) -> + list_to_integer(S). + +%% splitwith/3 + +splitwith(Fun, [H|T]) -> + {SH, ST} = lists:splitwith(Fun, T), + {[H|SH], ST}. + +is_eol_ch(C) -> + C == $\n orelse C == $\r. + +is_name_ch(C) -> + is_alphanum(C) orelse C == $- orelse C == $_. + +is_alphanum(C) -> + is_lower(C) orelse is_upper(C) orelse is_digit(C). + +is_lower(C) -> + $a =< C andalso C =< $z. + +is_upper(C) -> + $A =< C andalso C =< $Z. + +is_digit(C) -> + $0 =< C andalso C =< $9. diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl new file mode 100644 index 0000000000..36a6efa294 --- /dev/null +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -0,0 +1,1358 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% This module turns a dictionary file into the orddict that +%% diameter_codegen.erl in turn morphs into .erl and .hrl files for +%% encode and decode of Diameter messages and AVPs. +%% + +-module(diameter_dict_util). + +-export([parse/2, + format_error/1, + format/1]). + +-include("diameter_vsn.hrl"). + +-define(RETURN(T), throw({T, ?MODULE, ?LINE})). +-define(RETURN(T, Args), ?RETURN({T, Args})). + +-define(A, list_to_atom). +-define(L, atom_to_list). +-define(I, integer_to_list). +-define(F, io_lib:format). + +%% =========================================================================== +%% parse/2 +%% =========================================================================== + +-spec parse(File, Opts) + -> {ok, orddict:orddict()} + | {error, term()} + when File :: {path, string()} + | iolist() + | binary(), + Opts :: list(). + +parse(File, Opts) -> + putr(verbose, lists:member(verbose, Opts)), + try + {ok, do_parse(File, Opts)} + catch + {Reason, ?MODULE, _Line} -> + {error, Reason} + after + eraser(verbose) + end. + +%% =========================================================================== +%% format_error/1 +%% =========================================================================== + +format_error({read, Reason}) -> + file:format_error(Reason); +format_error({scan, Reason}) -> + diameter_dict_scanner:format_error(Reason); +format_error({parse, {Line, _Mod, Reason}}) -> + lists:flatten(["Line ", ?I(Line), ", ", Reason]); + +format_error(T) -> + {Fmt, As} = fmt(T), + lists:flatten(io_lib:format(Fmt, As)). + +fmt({avp_code_already_defined = E, [Code, false, Name, Line, L]}) -> + {fmt(E), [Code, "", Name, Line, L]}; +fmt({avp_code_already_defined = E, [Code, Vid, Name, Line, L]}) -> + {fmt(E), [Code, ?F("/~p", [Vid]), Name, Line, L]}; + +fmt({uint32_out_of_range = E, [id | T]}) -> + {fmt(E), ["@id", "application identifier" | T]}; +fmt({uint32_out_of_range = E, [K | T]}) + when K == vendor; + K == avp_vendor_id -> + {fmt(E), [?F("@~p", [K]), "vendor id" | T]}; +fmt({uint32_out_of_range = E, [K, Name | T]}) + when K == enum; + K == define -> + {fmt(E), [?F("@~p ~s", [K, Name]), "value" | T]}; +fmt({uint32_out_of_range = E, [avp_types, Name | T]}) -> + {fmt(E), ["AVP " ++ Name, "AVP code" | T]}; +fmt({uint32_out_of_range = E, [grouped, Name | T]}) -> + {fmt(E), ["Grouped AVP " ++ Name | T]}; +fmt({uint32_out_of_range = E, [messages, Name | T]}) -> + {fmt(E), ["Message " ++ Name, "command code" | T]}; + +fmt({Reason, As}) -> + {fmt(Reason), As}; + +fmt(avp_code_already_defined) -> + "AVP ~p~s (~s) at line ~p already defined at line ~p"; + +fmt(uint32_out_of_range) -> + "~s specifies ~s ~p at line ~p that is out of range for a value of " + "Diameter type Unsigned32"; + +fmt(imported_avp_already_defined) -> + "AVP ~s imported by @inherits ~p at line ~p defined at line ~p"; +fmt(duplicate_import) -> + "AVP ~s is imported by more than one @inherits, both at line ~p " + "and at line ~p"; + +fmt(duplicate_section) -> + "Section @~s at line ~p already declared at line ~p"; + +fmt(already_declared) -> + "Section @~p ~s at line ~p already declared at line ~p"; + +fmt(inherited_avp_already_defined) -> + "AVP ~s inherited at line ~p defined in @avp_types at line ~p"; +fmt(avp_already_defined) -> + "AVP ~s at line ~p already in @~p at line ~p"; +fmt(key_already_defined) -> + "Value for ~s:~s in @~p at line ~p already provided at line ~p"; + +fmt(messages_without_id) -> + "@messages at line ~p but @id not declared"; + +fmt(avp_name_already_defined) -> + "AVP ~s at line ~p already defined at line ~p"; +fmt(avp_has_unknown_type) -> + "AVP ~s at line ~p defined with unknown type ~s"; +fmt(avp_has_invalid_flag) -> + "AVP ~s at line ~p specifies invalid flag ~c"; +fmt(avp_has_duplicate_flag) -> + "AVP ~s at line ~p specifies duplicate flag ~c"; +fmt(avp_has_vendor_id) -> + "AVP ~s at line ~p does not specify V flag " + "but is assigned vendor id ~p at line ~p"; +fmt(avp_has_no_vendor) -> + "AVP ~s at line ~p specifies V flag " + "but neither @vendor_avp_id nor @vendor supplies a value"; + +fmt(group_already_defined) -> + "Group ~s at line ~p already defined at line ~p"; +fmt(grouped_avp_code_mismatch) -> + "AVP ~s at line ~p has with code ~p " + "but @avp_types specifies ~p at line ~p"; +fmt(grouped_avp_has_wrong_type) -> + "Grouped AVP ~s at line ~p defined with type ~s at line ~p"; +fmt(grouped_avp_not_defined) -> + "Grouped AVP ~s on line ~p not defined in @avp_types"; +fmt(grouped_vendor_id_without_flag) -> + "Grouped AVP ~s at line ~p has vendor id " + "but definition at line ~p does not specify V flag"; +fmt(grouped_vendor_id_mismatch) -> + "Grouped AVP ~s at line ~p has vendor id ~p " + "but ~p specified at line ~p"; + +fmt(message_name_already_defined) -> + "Message ~s at line ~p already defined at line ~p"; +fmt(message_code_already_defined) -> + "~s message with code ~p at line ~p already defined at line ~p"; +fmt(message_has_duplicate_flag) -> + "Message ~s has duplicate flag ~s at line ~p"; +fmt(message_application_id_mismatch) -> + "Message ~s has application id ~p at line ~p " + "but @id specifies ~p at line ~p"; + +fmt(invalid_avp_order) -> + "AVP reference ~c~s~c at line ~p breaks fixed/required/optional order"; +fmt(required_avp_has_zero_max_arity) -> + "Required AVP has maximum arity 0 at line ~p"; +fmt(required_avp_has_zero_min_arity) -> + "Required AVP has minimum arity 0 at line ~p"; +fmt(optional_avp_has_nonzero_min_arity) -> + "Optional AVP has non-zero minimum arity at line ~p"; +fmt(qualifier_has_min_greater_than_max) -> + "Qualifier ~p*~p at line ~p has Min > Max"; +fmt(avp_already_referenced) -> + "AVP ~s at line ~p already referenced at line ~p"; + +fmt(message_missing) -> + "~s message at line ~p but no ~s message is defined"; + +fmt(requested_avp_not_found) -> + "@inherit ~s at line ~p requests AVP ~s at line ~p " + "but module does not define that AVP"; + +fmt(enumerated_avp_has_wrong_local_type) -> + "Enumerated AVP ~s in @enum at line ~p defined with type ~s at line ~p"; +fmt(enumerated_avp_has_wrong_inherited_type) -> + "Enumerated AVP ~s in @enum at line ~p " + "inherited with type ~s from module ~s at line ~p"; +fmt(enumerated_avp_not_defined) -> + "Enumerated AVP ~s in @enum at line ~p neither defined nor inherited"; + +fmt(avp_not_defined) -> + "AVP ~s referenced at line ~p neither defined nor inherited"; + +fmt(recompile) -> + "Module ~p appears to have been compiler with an incompatible " + "version of the dictionary compiler and must be recompiled"; +fmt(not_loaded) -> + "Module ~p is not on the code path or could not be loaded"; +fmt(no_dict) -> + "Module ~p does not appear to be a diameter dictionary". + +%% =========================================================================== +%% format/1 +%% +%% Turn dict/0 output back into a dictionary file (with line ending = $\n). + +-spec format(Dict) + -> iolist() + when Dict :: orddict:orddict(). + +-define(KEYS, [id, name, prefix, vendor, + inherits, codecs, custom_types, + avp_types, + messages, + grouped, + enum, define]). + +format(Dict) -> + Io = orddict:fold(fun io/3, [], Dict), + [S || {_,S} <- lists:sort(fun keysort/2, Io)]. + +keysort({A,_}, {B,_}) -> + [HA, HB] = [H || K <- [A,B], + H <- [lists:takewhile(fun(X) -> X /= K end, ?KEYS)]], + HA < HB. + +%% =========================================================================== + +-define(INDENT, " "). +-define(SP, " "). +-define(NL, $\n). + +%% io/3 + +io(K, _, Acc) + when K == command_codes; + K == import_avps; + K == import_groups; + K == import_enums -> + Acc; + +io(Key, Body, Acc) -> + [{Key, io(Key, Body)} | Acc]. + +%% io/2 + +io(K, Id) + when K == id; + K == name; + K == prefix -> + [?NL, section(K), ?SP, tok(Id)]; + +io(vendor = K, {Id, Name}) -> + [?NL, section(K) | [[?SP, tok(X)] || X <- [Id, Name]]]; + +io(avp_types = K, Body) -> + [?NL, ?NL, section(K), ?NL, [body(K,A) || A <- Body]]; + +io(K, Body) + when K == messages; + K == grouped -> + [?NL, ?NL, section(K), [body(K,A) || A <- Body]]; + +io(K, Body) + when K == avp_vendor_id; + K == inherits; + K == custom_types; + K == codecs; + K == enum; + K == define -> + [[?NL, pairs(K, T)] || T <- Body]. + +pairs(K, {Id, Avps}) -> + [?NL, section(K), ?SP, tok(Id), ?NL, [[?NL, body(K, A)] || A <- Avps]]. + +body(K, AvpName) + when K == avp_vendor_id; + K == inherits; + K == custom_types; + K == codecs -> + [?INDENT, word(AvpName)]; + +body(K, {Name, N}) + when K == enum; + K == define -> + [?INDENT, word(Name), ?SP, ?I(N)]; + +body(avp_types = K, {Name, Code, Type, ""}) -> + body(K, {Name, Code, Type, "-"}); +body(avp_types, {Name, Code, Type, Flags}) -> + [?NL, ?INDENT, word(Name), + [[?SP, ?SP, S] || S <- [?I(Code), Type, Flags]]]; + +body(messages, {"answer-message", _, _, [], Avps}) -> + [?NL, ?NL, ?INDENT, + "answer-message ::= < Diameter Header: code, ERR [PXY] >", + f_avps(Avps)]; +body(messages, {Name, Code, Flags, ApplId, Avps}) -> + [?NL, ?NL, ?INDENT, word(Name), " ::= ", header(Code, Flags, ApplId), + f_avps(Avps)]; + +body(grouped, {Name, Code, Vid, Avps}) -> + [?NL, ?NL, ?INDENT, word(Name), " ::= ", avp_header(Code, Vid), + f_avps(Avps)]. + +header(Code, Flags, ApplId) -> + ["< Diameter Header: ", + ?I(Code), + [[", ", ?L(F)] || F <- Flags], + [[" ", ?I(N)] || N <- ApplId], + " >"]. + +avp_header(Code, Vid) -> + ["< AVP Header: ", + ?I(Code), + [[" ", ?I(V)] || V <- Vid], + " >"]. + +f_avps(L) -> + [[?NL, ?INDENT, ?INDENT, f_avp(A)] || A <- L]. + +f_avp({Q, A}) -> + [D | _] = Avp = f_delim(A), + f_avp(f_qual(D, Q), Avp); +f_avp(A) -> + f_avp("", f_delim(A)). + +f_delim({{A}}) -> + [$<, word(A), $>]; +f_delim({A}) -> + [${, word(A), $}]; +f_delim([A]) -> + [$[, word(A), $]]. + +f_avp(Q, [L, Avp, R]) -> + Len = length(lists:flatten([Q])), + [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), L, " ", Avp, " ", R]. + +f_qual(${, '*') -> + "1*"; %% Equivalent to "*" but the more common/obvious rendition +f_qual(_, '*') -> + "*"; +f_qual(_, {'*', N}) -> + [$*, ?I(N)]; +f_qual(_, {N, '*'}) -> + [?I(N), $*]; +f_qual(_, {M,N}) -> + [?I(M), $*, ?I(N)]. + +section(Key) -> + ["@", ?L(Key)]. + +tok(N) + when is_integer(N) -> + ?I(N); +tok(N) -> + word(N). + +word(Str) -> + word(diameter_dict_scanner:is_name(Str), Str). + +word(true, Str) -> + Str; +word(false, Str) -> + [$', Str, $']. + +%% =========================================================================== + +do_parse(File, Opts) -> + Bin = do([fun read/1, File], read), + Toks = do([fun diameter_dict_scanner:scan/1, Bin], scan), + Tree = do([fun diameter_dict_parser:parse/1, Toks], parse), + make_dict(Tree, Opts). + +do([F|A], E) -> + case apply(F,A) of + {ok, T} -> + T; + {error, Reason} -> + ?RETURN({E, Reason}) + end. + +read({path, Path}) -> + file:read_file(Path); +read(File) -> + {ok, iolist_to_binary([File])}. + +make_dict(Parse, Opts) -> + make_orddict(pass4(pass3(pass2(pass1(reset(make_dict(Parse), + Opts))), + Opts))). + +%% make_orddict/1 + +make_orddict(Dict) -> + dict:fold(fun mo/3, + orddict:from_list([{K,[]} || K <- [avp_types, + messages, + grouped, + inherits, + custom_types, + codecs, + avp_vendor_id, + enum, + define]]), + Dict). + +mo(K, Sects, Dict) + when is_atom(K) -> + orddict:store(K, make(K, Sects), Dict); + +mo(_, _, Dict) -> + Dict. + +make(K, [[_Line, {_, _, X}]]) + when K == id; + K == name; + K == prefix -> + X; + +make(vendor, [[_Line, {_, _, Id}, {_, _, Name}]]) -> + {Id, Name}; + +make(K, T) + when K == command_codes; + K == import_avps; + K == import_groups; + K == import_enums -> + T; + +make(K, Sects) -> + post(K, foldl(fun([_L|B], A) -> make(K,B,A) end, + [], + Sects)). + +post(avp_types, L) -> + lists:sort(L); + +post(K, L) + when K == grouped; + K == messages; + K == enum; + K == define -> + lists:reverse(L); + +post(_, L) -> + L. + +make(K, [{_,_,Name} | Body], Acc) + when K == enum; + K == define; + K == avp_vendor_id; + K == custom_types; + K == inherits; + K == codecs -> + [{Name, mk(K, Body)} | Acc]; + +make(K, Body, Acc) -> + foldl(fun(T,A) -> [mk(K, T) | A] end, Acc, Body). + +mk(avp_types, [{_,_,Name}, {_,_,Code}, {_,_,Type}, {_,_,Flags}]) -> + {Name, Code, type(Type), Flags}; + +mk(messages, [{'answer-message' = A, _}, false | Avps]) -> + {?L(A), -1, ['ERR', 'PXY'], [], make_body(Avps)}; + +mk(messages, [{_,_,Name}, [{_,_,Code}, Flags, ApplId] | Avps]) -> + {Name, + Code, + lists:map(fun({F,_}) -> F end, Flags), + opt(ApplId), + make_body(Avps)}; + +mk(grouped, [{_,_,Name}, [{_,_,Code}, Vid] | Avps]) -> + {Name, Code, opt(Vid), make_body(Avps)}; + +mk(K, Body) + when K == enum; + K == define -> + lists:map(fun([{_,_,Name}, {_,_,Value}]) -> {Name, Value} end, Body); + +mk(K, Avps) + when K == avp_vendor_id; + K == custom_types; + K == inherits; + K == codecs -> + lists:map(fun({_,_,N}) -> N end, Avps). + +opt(false) -> + []; +opt({_,_,X}) -> + [X]. + +make_body(Avps) -> + lists:map(fun avp/1, Avps). + +avp([false, D, Avp]) -> + avp(D, Avp); +avp([Q, D, Avp]) -> + case {qual(D, Q), avp(D, Avp)} of + {{0,1}, A} when D == $[ -> + A; + {{1,1}, A} -> + A; + T -> + T + end. +%% Could just store the qualifier as a pair in all cases but the more +%% compact form is easier to parse visually so live with a bit of +%% mapping. Ditto the use of '*'. + +avp(D, {'AVP', _}) -> + delim(D, "AVP"); +avp(D, {_, _, Name}) -> + delim(D, Name). + +delim($<, N) -> + {{N}}; +delim(${, N) -> + {N}; +delim($[, N) -> + [N]. + +%% There's a difference between max = 0 and not specifying an AVP: +%% reception of an AVP with max = 0 will always be an error, otherwise +%% it depends on the existence of 'AVP' and the M flag. + +qual(${, {{_,L,0}, _}) -> + ?RETURN(required_avp_has_zero_min_arity, [L]); +qual(${, {_, {_,L,0}}) -> + ?RETURN(required_avp_has_zero_max_arity, [L]); + +qual($[, {{_,L,N}, _}) + when 0 < N -> + ?RETURN(optional_avp_has_nonzero_min_arity, [L]); + +qual(_, {{_,L,Min}, {_,_,Max}}) + when Min > Max -> + ?RETURN(qualifier_has_min_greater_than_max, [Min, Max, L]); + +qual(_, true) -> + '*'; + +qual(${, {true, {_,_,N}}) -> + {1, N}; +qual(_, {true, {_,_,N}}) -> + {0, N}; + +qual(D, {{_,_,N}, true}) + when D == ${, N == 1; + D /= ${, N == 0 -> + '*'; +qual(_, {{_,_,N}, true}) -> + {N, '*'}; + +qual(_, {{_,_,Min}, {_,_,Max}}) -> + {Min, Max}. + +%% Optional reports when running verbosely. +report(What, [F | A]) + when is_function(F) -> + report(What, apply(F, A)); +report(What, Data) -> + report(getr(verbose), What, Data). + +report(true, Tag, Data) -> + io:format("##~n## ~p ~p~n", [Tag, Data]); +report(false, _, _) -> + ok. + +%% ------------------------------------------------------------------------ +%% make_dict/1 +%% +%% Turn a parsed dictionary into an dict. + +make_dict(Parse) -> + foldl(fun(T,A) -> + report(section, T), + section(T,A) + end, + dict:new(), + Parse). + +section([{T, L} | Rest], Dict) + when T == name; + T == prefix; + T == id; + T == vendor -> + case find(T, Dict) of + [] -> + dict:store(T, [[L | Rest]], Dict); + [[Line | _]] -> + ?RETURN(duplicate_section, [T, L, Line]) + end; + +section([{T, L} | Rest], Dict) + when T == avp_types; + T == messages; + T == grouped; + T == inherits; + T == custom_types; + T == codecs; + T == avp_vendor_id; + T == enum; + T == define -> + dict:append(T, [L | Rest], Dict). + +%% =========================================================================== +%% reset/2 +%% +%% Reset sections from options. + +reset(Dict, Opts) -> + foldl([fun reset/3, Opts], Dict, [name, prefix, inherits]). + +reset(K, Dict, Opts) -> + foldl(fun opt/2, Dict, [T || {A,_} = T <- Opts, A == K]). + +opt({inherits = Key, "-"}, Dict) -> + dict:erase(Key, Dict); + +opt({inherits = Key, Mod}, Dict) -> + case lists:splitwith(fun(C) -> C /= $/ end, Mod) of + {Mod, ""} -> + dict:append(Key, [0, {word, 0, Mod}], Dict); + {From, [$/|To]} -> + dict:store(Key, + [reinherit(From, To, M) || M <- find(Key, Dict)], + Dict) + end; + +opt({Key, Val}, Dict) -> + dict:store(Key, [[0, {word, 0, Val}]], Dict); + +opt(_, Dict) -> + Dict. + +reinherit(From, To, [L, {word, _, From} = T | Avps]) -> + [L, setelement(3, T, To) | Avps]; +reinherit(_, _, T) -> + T. + +%% =========================================================================== +%% pass1/1 +%% +%% Explode sections into additional dictionary entries plus semantic +%% checks. + +pass1(Dict) -> + true = no_messages_without_id(Dict), + + foldl(fun(K,D) -> foldl([fun p1/3, K], D, find(K,D)) end, + Dict, + [id, + vendor, + avp_types, %% must precede inherits, grouped, enum + avp_vendor_id, + custom_types, + codecs, + inherits, + grouped, + messages, + enum, + define]). + +%% Multiple sections are allowed as long as their bodies don't +%% overlap. (Except enum/define.) + +p1([_Line, N], Dict, id = K) -> + true = is_uint32(N, [K]), + Dict; + +p1([_Line, Id, _Name], Dict, vendor = K) -> + true = is_uint32(Id, [K]), + Dict; + +p1([_Line, X | Body], Dict, K) + when K == avp_vendor_id; + K == custom_types; + K == codecs; + K == inherits -> + foldl([fun explode/4, X, K], Dict, Body); + +p1([_Line, X | Body], Dict, K) + when K == define; + K == enum -> + {_, L, Name} = X, + foldl([fun explode2/4, X, K], + store_new({K, Name}, + [L, Body], + Dict, + [K, Name, L], + already_declared), + Body); + +p1([_Line | Body], Dict, K) + when K == avp_types; + K == grouped; + K == messages -> + foldl([fun explode/3, K], Dict, Body). + +no_messages_without_id(Dict) -> + case find(messages, Dict) of + [] -> + true; + [[Line | _] | _] -> + [] /= find(id, Dict) orelse ?RETURN(messages_without_id, [Line]) + end. + +%% Note that the AVP's in avp_vendor_id, custom_types, codecs and +%% enum can all be inherited, as can the AVP content of messages and +%% grouped AVP's. Check that the referenced AVP's exist after +%% importing definitions. + +%% explode/4 +%% +%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()] +%% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()] + +explode({_, Line, AvpName}, Dict, {_, _, X} = T, K) -> + true = K /= avp_vendor_id orelse is_uint32(T, [K]), + true = K /= inherits orelse avp_not_local(AvpName, Line, Dict), + + store_new({key(K), AvpName}, + [Line, X], + Dict, + [AvpName, Line, K], + avp_already_defined). + +%% explode2/4 + +%% {define, {Name, Key}} -> [Lineno, Value::integer(), enum|define] + +explode2([{_, Line, Key}, {_, _, Value} = T], Dict, {_, _, Name}, K) -> + true = is_uint32(T, [K, Name]), + + store_new({key(K), {Name, Key}}, + [Line, Value, K], + Dict, + [Name, Key, K, Line], + key_already_defined). + +%% key/1 +%% +%% Conflate keys that are equivalent as far as uniqueness of +%% definition goes. + +key(K) + when K == enum; + K == define -> + define; +key(K) + when K == custom_types; + K == codecs -> + custom; +key(K) -> + K. + +%% explode/3 + +%% {avp_types, AvpName} -> [Line | Toks] +%% {avp_types, {Code, IsReq}} -> [Line, AvpName] +%% +%% where AvpName = string() +%% Code = integer() +%% IsReq = boolean() + +explode([{_, Line, Name} | Toks], Dict0, avp_types = K) -> + %% Each AVP can be defined only once. + Dict = store_new({K, Name}, + [Line | Toks], + Dict0, + [Name, Line], + avp_name_already_defined), + + [{number, _, _Code} = C, {word, _, Type}, {word, _, _Flags}] = Toks, + + true = avp_type_known(Type, Name, Line), + true = is_uint32(C, [K, Name]), + + Dict; + +%% {grouped, Name} -> [Line, HeaderTok | AvpToks] +%% {grouped, {Name, AvpName}} -> [Line, Qual, Delim] +%% +%% where Name = string() +%% AvpName = string() +%% Qual = {Q, Q} | boolean() +%% Q = true | NumberTok +%% Delim = $< | ${ | $[ + +explode([{_, Line, Name}, Header | Avps], Dict0, grouped = K) -> + Dict = store_new({K, Name}, + [Line, Header | Avps], + Dict0, + [Name, Line], + group_already_defined), + + [{_,_, Code} = C, Vid] = Header, + {DefLine, {_, _, Flags}} = grouped_flags(Name, Code, Dict0, Line), + V = lists:member($V, Flags), + + true = is_uint32(C, [K, Name, "AVP code"]), + true = is_uint32(Vid, [K, Name, "vendor id"]), + false = vendor_id_mismatch(Vid, V, Name, Dict0, Line, DefLine), + + explode_avps(Avps, Dict, K, Name); + +%% {messages, Name} -> [Line, HeaderTok | AvpToks] +%% {messages, {Code, IsReq}} -> [Line, NameTok] +%% {messages, Code} -> [[Line, NameTok, IsReq]] +%% {messages, {Name, Flag}} -> [Line] +%% {messages, {Name, AvpName}} -> [Line, Qual, Delim] +%% +%% where Name = string() +%% Code = integer() +%% IsReq = boolean() +%% Flag = 'REQ' | 'PXY' +%% AvpName = string() +%% Qual = true | {Q,Q} +%% Q = true | NumberTok +%% Delim = $< | ${ | ${ + +explode([{'answer-message' = A, Line}, false = H | Avps], + Dict0, + messages = K) -> + Name = ?L(A), + Dict1 = store_new({K, Name}, + [Line, H, Avps], + Dict0, + [Name, Line], + message_name_already_defined), + + explode_avps(Avps, Dict1, K, Name); + +explode([{_, Line, MsgName} = M, Header | Avps], + Dict0, + messages = K) -> + %% There can be at most one message with a given name. + Dict1 = store_new({K, MsgName}, + [Line, Header | Avps], + Dict0, + [MsgName, Line], + message_name_already_defined), + + [{_, _, Code} = C, Bits, ApplId] = Header, + + %% Don't check any application id since it's required to be + %% the same as @id. + true = is_uint32(C, [K, MsgName]), + + %% An application id specified as part of the message definition + %% has to agree with @id. The former is parsed just because RFC + %% 3588 specifies it. + false = application_id_mismatch(ApplId, Dict1, MsgName), + + IsReq = lists:keymember('REQ', 1, Bits), + + %% For each command code, there can be at most one request and + %% one answer. + Dict2 = store_new({K, {Code, IsReq}}, + [Line, M], + Dict1, + [choose(IsReq, "Request", "Answer"), Code, Line], + message_code_already_defined), + + %% For each message, each flag can occur at most once. + Dict3 = foldl(fun({F,L},D) -> + store_new({K, {MsgName, F}}, + [L], + D, + [MsgName, ?L(F)], + message_has_duplicate_flag) + end, + Dict2, + Bits), + + dict:append({K, Code}, + [Line, M, IsReq], + explode_avps(Avps, Dict3, K, MsgName)). + +%% explode_avps/4 +%% +%% Ensure required AVP order and sane qualifiers. Can't check for AVP +%% names until after they've been imported. +%% +%% RFC 3588 allows a trailing fixed while 3588bis doesn't. Parse the +%% former. + +explode_avps(Avps, Dict, Key, Name) -> + xa("<{[<", Avps, Dict, Key, Name). + +xa(_, [], Dict, _, _) -> + Dict; + +xa(Ds, [[Qual, D, {'AVP', Line}] | Avps], Dict, Key, Name) -> + xa(Ds, [[Qual, D, {word, Line, "AVP"}] | Avps], Dict, Key, Name); + +xa([], [[_Qual, D, {_, Line, Name}] | _], _, _, _) -> + ?RETURN(invalid_avp_order, [D, Name, close(D), Line]); + +xa([D|_] = Ds, [[Qual, D, {_, Line, AvpName}] | Avps], Dict, Key, Name) -> + xa(Ds, + Avps, + store_new({Key, {Name, AvpName}}, + [Line, Qual, D], + Dict, + [Name, Line], + avp_already_referenced), + Key, + Name); + +xa([_|Ds], Avps, Dict, Key, Name) -> + xa(Ds, Avps, Dict, Key, Name). + +close($<) -> $>; +close(${) -> $}; +close($[) -> $]. + +%% is_uint32/2 + +is_uint32(false, _) -> + true; +is_uint32({Line, _, N}, Args) -> + N < 1 bsl 32 orelse ?RETURN(uint32_out_of_range, Args ++ [N, Line]). +%% Can't call diameter_types here since it may not exist yet. + +%% application_id_mismatch/3 + +application_id_mismatch({number, Line, Id}, Dict, MsgName) -> + [[_, {_, L, I}]] = dict:fetch(id, Dict), + + I /= Id andalso ?RETURN(message_application_id_mismatch, + [MsgName, Id, Line, I, L]); + +application_id_mismatch(false = No, _, _) -> + No. + +%% avp_not_local/3 + +avp_not_local(Name, Line, Dict) -> + A = find({avp_types, Name}, Dict), + + [] == A orelse ?RETURN(inherited_avp_already_defined, + [Name, Line, hd(A)]). + +%% avp_type_known/3 + +avp_type_known(Type, Name, Line) -> + false /= type(Type) + orelse ?RETURN(avp_has_unknown_type, [Name, Line, Type]). + +%% vendor_id_mismatch/6 +%% +%% Require a vendor id specified on a group to match any specified +%% in @avp_vendor_id. Note that both locations for the value are +%% equivalent, both in the value being attributed to a locally +%% defined AVP and ignored when imported from another dictionary. + +vendor_id_mismatch({_,_,_}, false, Name, _, Line, DefLine) -> + ?RETURN(grouped_vendor_id_without_flag, [Name, Line, DefLine]); + +vendor_id_mismatch({_, _, I}, true, Name, Dict, Line, _) -> + case vendor_id(Name, Dict) of + {avp_vendor_id, L, N} -> + I /= N andalso + ?RETURN(grouped_vendor_id_mismatch, [Name, Line, I, N, L]); + _ -> + false + end; + +vendor_id_mismatch(_, _, _, _, _, _) -> + false. + +%% grouped_flags/4 + +grouped_flags(Name, Code, Dict, Line) -> + case find({avp_types, Name}, Dict) of + [L, {_, _, Code}, {_, _, "Grouped"}, Flags] -> + {L, Flags}; + [_, {_, L, C}, {_, _, "Grouped"}, _Flags] -> + ?RETURN(grouped_avp_code_mismatch, [Name, Line, Code, C, L]); + [_, _Code, {_, L, T}, _] -> + ?RETURN(grouped_avp_has_wrong_type, [Name, Line, T, L]); + [] -> + ?RETURN(grouped_avp_not_defined, [Name, Line]) + end. + +%% vendor_id/2 + +%% Look for a vendor id in @avp_vendor_id, then @vendor. +vendor_id(Name, Dict) -> + case find({avp_vendor_id, Name}, Dict) of + [Line, Id] when is_integer(Id) -> + {avp_vendor_id, Line, Id}; + [] -> + vendor(Dict) + end. + +vendor(Dict) -> + case find(vendor, Dict) of + [[_Line, {_, _, Id}, {_, _, _}]] -> + {vendor, Id}; + [] -> + false + end. + +%% find/2 + +find(Key, Dict) -> + case dict:find(Key, Dict) of + {ok, L} when is_list(L) -> + L; + error -> + [] + end. + +%% store_new/5 + +store_new(Key, Value, Dict, Args, Err) -> + case dict:find(Key, Dict) of + {ok, [L | _]} -> + ?RETURN(Err, Args ++ [L]); + error -> + dict:store(Key, Value, Dict) + end. + +%% type/1 + +type("DiamIdent") -> + "DiameterIdentity"; +type("DiamURI") -> + "DiameterURI"; +type(T) + when T == "OctetString"; + T == "Integer32"; + T == "Integer64"; + T == "Unsigned32"; + T == "Unsigned64"; + T == "Float32"; + T == "Float64"; + T == "Grouped"; + T == "Enumerated"; + T == "Address"; + T == "Time"; + T == "UTF8String"; + T == "DiameterIdentity"; + T == "DiameterURI"; + T == "IPFilterRule"; + T == "QoSFilterRule" -> + T; +type(_) -> + false. + +%% =========================================================================== +%% pass2/1 +%% +%% More explosion, but that requires the previous pass to write its +%% entries. + +pass2(Dict) -> + foldl(fun(K,D) -> foldl([fun p2/3, K], D, find(K,D)) end, + Dict, + [avp_types]). + +p2([_Line | Body], Dict, avp_types) -> + foldl(fun explode_avps/2, Dict, Body); + +p2([], Dict, _) -> + Dict. + +explode_avps([{_, Line, Name} | Toks], Dict) -> + [{number, _, Code}, {word, _, _Type}, {word, _, Flags}] = Toks, + + true = avp_flags_valid(Flags, Name, Line), + + 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. + store_new({avp_types, {Code, Vid}}, + [Line, Name], + Dict, + [Code, Vid, Name, Line], + avp_code_already_defined). + +%% avp_flags_valid/3 + +avp_flags_valid(Flags, Name, Line) -> + Bad = lists:filter(fun(C) -> not lists:member(C, "MVP") end, Flags), + [] == Bad + orelse ?RETURN(avp_has_invalid_flag, [Name, Line, hd(Bad)]), + + Dup = Flags -- "MVP", + [] == Dup + orelse ?RETURN(avp_has_duplicate_flag, [Name, Line, hd(Dup)]). + +%% avp_vendor_id/4 + +avp_vendor_id(Flags, Name, Line, Dict) -> + V = lists:member($V, Flags), + + case vendor_id(Name, Dict) of + {avp_vendor_id, _, I} when V -> + I; + {avp_vendor_id, L, I} -> + ?RETURN(avp_has_vendor_id, [Name, Line, I, L]); + {vendor, I} when V -> + I; + false when V -> + ?RETURN(avp_has_no_vendor, [Name, Line]); + _ -> + false + end. + +%% =========================================================================== +%% pass3/2 +%% +%% Import AVPs. + +pass3(Dict, Opts) -> + import_enums(import_groups(import_avps(insert_codes(Dict), Opts))). + +%% insert_codes/1 +%% +%% command_codes -> [{Code, ReqNameTok, AnsNameTok}] + +insert_codes(Dict) -> + dict:store(command_codes, + dict:fold(fun make_code/3, [], Dict), + Dict). + +make_code({messages, Code}, Names, Acc) + when is_integer(Code) -> + [mk_code(Code, Names) | Acc]; +make_code(_, _, Acc) -> + Acc. + +mk_code(Code, [[_, _, false] = Ans, [_, _, true] = Req]) -> + mk_code(Code, [Req, Ans]); + +mk_code(Code, [[_, {_,_,Req}, true], [_, {_,_,Ans}, false]]) -> + {Code, Req, Ans}; + +mk_code(_Code, [[Line, _Name, IsReq]]) -> + ?RETURN(message_missing, [choose(IsReq, "Request", "Answer"), + Line, + choose(IsReq, "answer", "request")]). + +%% import_avps/2 + +import_avps(Dict, Opts) -> + Import = inherit(Dict, Opts), + report(imported, Import), + + %% pass4/1 tests that all referenced AVP's are either defined + %% or imported. + + dict:store(import_avps, + lists:map(fun({M, _, As}) -> {M, [A || {_,A} <- As]} end, + lists:reverse(Import)), + foldl(fun explode_imports/2, Dict, Import)). + +explode_imports({Mod, Line, Avps}, Dict) -> + foldl([fun xi/4, Mod, Line], Dict, Avps). + +xi({L, {Name, _Code, _Type, _Flags} = A}, Dict, Mod, Line) -> + store_new({avp_types, Name}, + [0, Mod, Line, L, A], + store_new({import, Name}, + [Line], + Dict, + [Name, Line], + duplicate_import), + [Name, Mod, Line], + imported_avp_already_defined). + +%% import_groups/1 +%% import_enums/1 +%% +%% For each inherited module, store the content of imported AVP's of +%% type grouped/enumerated in a new key. + +import_groups(Dict) -> + dict:store(import_groups, import(grouped, Dict), Dict). + +import_enums(Dict) -> + dict:store(import_enums, import(enum, Dict), Dict). + +import(Key, Dict) -> + flatmap([fun import_key/2, Key], dict:fetch(import_avps, Dict)). + +import_key({Mod, Avps}, Key) -> + As = lists:flatmap(fun(T) -> + N = element(1,T), + choose(lists:keymember(N, 1, Avps), [T], []) + end, + orddict:fetch(Key, dict(Mod))), + if As == [] -> + []; + true -> + [{Mod, As}] + end. + +%% ------------------------------------------------------------------------ +%% inherit/2 +%% +%% Return a {Mod, Line, [{Lineno, Avp}]} list, where Mod is a module +%% name, Line points to the corresponding @inherit and each Avp is +%% from Mod:dict(). Lineno is 0 if the import is implicit. + +inherit(Dict, Opts) -> + code:add_pathsa([D || {include, D} <- Opts]), + foldl(fun inherit_avps/2, [], find(inherits, Dict)). +%% Note that the module order of the returned lists is reversed +%% relative to @inherits. + +inherit_avps([Line, {_,_,M} | Names], Acc) -> + Mod = ?A(M), + report(inherit_from, Mod), + case find_avps(Names, avps_from_module(Mod)) of + {_, [{_, L, N} | _]} -> + ?RETURN(requested_avp_not_found, [Mod, Line, N, L]); + {Found, []} -> + [{Mod, Line, lists:sort(Found)} | Acc] + end. + +%% Import everything not defined locally ... +find_avps([], Avps) -> + {[{0, A} || A <- Avps], []}; + +%% ... or specified AVPs. +find_avps(Names, Avps) -> + foldl(fun acc_avp/2, {[], Names}, Avps). + +acc_avp({Name, _Code, _Type, _Flags} = A, {Found, Not} = Acc) -> + case lists:keyfind(Name, 3, Not) of + {_, Line, Name} -> + {[{Line, A} | Found], lists:keydelete(Name, 3, Not)}; + false -> + Acc + end. + +%% avps_from_module/2 + +avps_from_module(Mod) -> + orddict:fetch(avp_types, dict(Mod)). + +dict(Mod) -> + try Mod:dict() of + [?VERSION | Dict] -> + Dict; + _ -> + ?RETURN(recompile, [Mod]) + catch + error: _ -> + ?RETURN(choose(false == code:is_loaded(Mod), + not_loaded, + no_dict), + [Mod]) + end. + +%% =========================================================================== +%% pass4/1 +%% +%% Sanity checks. + +pass4(Dict) -> + dict:fold(fun(K, V, _) -> p4(K, V, Dict) end, ok, Dict), + Dict. + +%% Ensure enum AVP's have type Enumerated. +p4({enum, Name}, [Line | _], Dict) + when is_list(Name) -> + true = is_enumerated_avp(Name, Dict, Line); + +%% Ensure all referenced AVP's are either defined locally or imported. +p4({K, {Name, AvpName}}, [Line | _], Dict) + when (K == grouped orelse K == messages), + is_list(Name), + is_list(AvpName), + AvpName /= "AVP" -> + true = avp_is_defined(AvpName, Dict, Line); + +%% Ditto. +p4({K, AvpName}, [Line | _], Dict) + when K == avp_vendor_id; + K == custom_types; + K == codecs -> + true = avp_is_defined(AvpName, Dict, Line); + +p4(_, _, _) -> + ok. + +%% has_enumerated_type/3 + +is_enumerated_avp(Name, Dict, Line) -> + case find({avp_types, Name}, Dict) of + [_Line, _Code, {_, _, "Enumerated"}, _Flags] -> %% local + true; + [_Line, _Code, {_, L, T}, _] -> + ?RETURN(enumerated_avp_has_wrong_local_type, + [Name, Line, T, L]); + [0, _, _, _, {_Name, _Code, "Enumerated", _Flags}] -> %% inherited + true; + [0, Mod, LM, LA, {_Name, _Code, Type, _Flags}] -> + ?RETURN(enumerated_avp_has_wrong_inherited_type, + [Name, Line, Type, Mod, choose(0 == LA, LM, LA)]); + [] -> + ?RETURN(enumerated_avp_not_defined, [Name, Line]) + end. + +avp_is_defined(Name, Dict, Line) -> + case find({avp_types, Name}, Dict) of + [_Line, _Code, _Type, _Flags] -> %% local + true; + [0, _, _, _, {Name, _Code, _Type, _Flags}] -> %% inherited + true; + [] -> + ?RETURN(avp_not_defined, [Name, Line]) + end. + +%% =========================================================================== + +putr(Key, Value) -> + put({?MODULE, Key}, Value). + +getr(Key) -> + get({?MODULE, Key}). + +eraser(Key) -> + erase({?MODULE, Key}). + +choose(true, X, _) -> X; +choose(false, _, X) -> X. + +foldl(F, Acc, List) -> + lists:foldl(fun(T,A) -> eval([F,T,A]) end, Acc, List). + +flatmap(F, List) -> + lists:flatmap(fun(T) -> eval([F,T]) end, List). + +eval([[F|X] | A]) -> + eval([F | A ++ X]); +eval([F|A]) -> + apply(F,A). diff --git a/lib/diameter/src/compiler/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl index 5e120d6f44..191f53f29d 100644 --- a/lib/diameter/src/compiler/diameter_exprecs.erl +++ b/lib/diameter/src/compiler/diameter_exprecs.erl @@ -96,41 +96,15 @@ -export([parse_transform/2]). -%% Form tag with line number. --define(F(T), T, ?LINE). -%% Yes, that's right. The replacement is to the first unmatched ')'. - --define(attribute, ?F(attribute)). --define(clause, ?F(clause)). --define(function, ?F(function)). --define(call, ?F(call)). --define('fun', ?F('fun')). --define(generate, ?F(generate)). --define(lc, ?F(lc)). --define(match, ?F(match)). --define(remote, ?F(remote)). --define(record, ?F(record)). --define(record_field, ?F(record_field)). --define(record_index, ?F(record_index)). --define(tuple, ?F(tuple)). - --define(ATOM(T), {atom, ?LINE, T}). --define(VAR(V), {var, ?LINE, V}). - --define(CALL(F,A), {?call, ?ATOM(F), A}). --define(APPLY(M,F,A), {?call, {?remote, ?ATOM(M), ?ATOM(F)}, A}). +-include("diameter_forms.hrl"). %% parse_transform/2 parse_transform(Forms, _Options) -> Rs = [R || {attribute, _, record, R} <- Forms], - case lists:append([E || {attribute, _, export_records, E} <- Forms]) of - [] -> - Forms; - Es -> - {H,T} = lists:splitwith(fun is_head/1, Forms), - H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T - end. + Es = lists:append([E || {attribute, _, export_records, E} <- Forms]), + {H,T} = lists:splitwith(fun is_head/1, Forms), + H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T. is_head(T) -> not lists:member(element(1,T), [function, eof]). @@ -200,7 +174,7 @@ fname(Op, Rname) -> '#info-/2'(Exports) -> {?function, fname(info), 2, - lists:map(fun 'info-'/1, Exports)}. + lists:map(fun 'info-'/1, Exports) ++ [?BADARG(2)]}. 'info-'(R) -> {?clause, [?ATOM(R), ?VAR('Info')], @@ -209,7 +183,7 @@ fname(Op, Rname) -> '#new-/1'(Exports) -> {?function, fname(new), 1, - lists:map(fun 'new-'/1, Exports)}. + lists:map(fun 'new-'/1, Exports) ++ [?BADARG(1)]}. 'new-'(R) -> {?clause, [?ATOM(R)], @@ -218,7 +192,7 @@ fname(Op, Rname) -> '#new-/2'(Exports) -> {?function, fname(new), 2, - lists:map(fun 'new--'/1, Exports)}. + lists:map(fun 'new--'/1, Exports) ++ [?BADARG(2)]}. 'new--'(R) -> {?clause, [?ATOM(R), ?VAR('Vals')], @@ -227,7 +201,7 @@ fname(Op, Rname) -> '#get-/2'(Exports) -> {?function, fname(get), 2, - lists:map(fun 'get-'/1, Exports)}. + lists:map(fun 'get-'/1, Exports) ++ [?BADARG(2)]}. 'get-'(R) -> {?clause, [?VAR('Attrs'), @@ -237,7 +211,7 @@ fname(Op, Rname) -> '#set-/2'(Exports) -> {?function, fname(set), 2, - lists:map(fun 'set-'/1, Exports)}. + lists:map(fun 'set-'/1, Exports) ++ [?BADARG(2)]}. 'set-'(R) -> {?clause, [?VAR('Vals'), {?match, {?record, R, []}, ?VAR('Rec')}], diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl index d93131df34..4cd86c32aa 100644 --- a/lib/diameter/src/compiler/diameter_forms.hrl +++ b/lib/diameter/src/compiler/diameter_forms.hrl @@ -21,6 +21,13 @@ %% Macros used when building abstract code. %% +%% Generated functions that could have no generated clauses will have +%% a trailing ?BADARG clause that should never execute as called +%% by diameter. +-define(BADARG(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)], + [], + [?APPLY(erlang, error, [?ATOM(badarg)])]}). + %% Form tag with line number. -define(F(T), T, ?LINE). %% Yes, that's right. The replacement is to the first unmatched ')'. diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl index 5380ee56ca..16e30c1ffb 100644 --- a/lib/diameter/src/compiler/diameter_make.erl +++ b/lib/diameter/src/compiler/diameter_make.erl @@ -20,59 +20,113 @@ %% %% Module alternative to diameterc for dictionary compilation. %% -%% Eg. 1> diameter_make:dict("mydict.dia"). +%% Eg. 1> diameter_make:codec("mydict.dia"). %% -%% $ erl -noshell \ +%% $ erl -noinput \ %% -boot start_clean \ -%% -s diameter_make dict mydict.dia \ +%% -eval 'ok = diameter_make:codec("mydict.dia")' \ %% -s init stop %% -module(diameter_make). --export([dict/1, +-export([codec/1, + codec/2, + dict/1, dict/2, - spec/1, - spec/2]). + format/1, + reformat/1]). --type opt() :: {outdir|include|name|prefix|inherits, string()} +-export_type([opt/0]). + +-type opt() :: {include|outdir|name|prefix|inherits, string()} | verbose | debug. -%% dict/1-2 +%% =========================================================================== + +%% codec/1-2 +%% +%% Parse a dictionary file and generate a codec module. + +-spec codec(Path, [opt()]) + -> ok + | {error, Reason} + when Path :: string(), + Reason :: string(). + +codec(File, Opts) -> + case dict(File, Opts) of + {ok, Dict} -> + make(File, + Opts, + Dict, + [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]); + {error, _} = E -> + E + end. + +codec(File) -> + codec(File, []). + +%% dict/2 +%% +%% Parse a dictionary file and return the orddict that a codec module +%% returns from dict/0. -spec dict(string(), [opt()]) - -> ok. + -> {ok, orddict:orddict()} + | {error, string()}. -dict(File, Opts) -> - make(File, - Opts, - spec(File, Opts), - [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]). +dict(Path, Opts) -> + case diameter_dict_util:parse({path, Path}, Opts) of + {ok, _} = Ok -> + Ok; + {error = E, Reason} -> + {E, diameter_dict_util:format_error(Reason)} + end. dict(File) -> dict(File, []). -%% spec/2 +%% format/1 +%% +%% Turn an orddict returned by dict/1-2 back into a dictionary file +%% in the form of an iolist(). + +-spec format(orddict:orddict()) + -> iolist(). + +format(Dict) -> + diameter_dict_util:format(Dict). --spec spec(string(), [opt()]) - -> orddict:orddict(). +%% reformat/1 +%% +%% Parse a dictionary file and return its formatted equivalent. -spec(File, Opts) -> - diameter_spec_util:parse(File, Opts). +-spec reformat(File) + -> {ok, iolist()} + | {error, Reason} + when File :: string(), + Reason :: string(). -spec(File) -> - spec(File, []). +reformat(File) -> + case dict(File) of + {ok, Dict} -> + {ok, format(Dict)}; + {error, _} = No -> + No + end. %% =========================================================================== make(_, _, _, []) -> ok; -make(File, Opts, Spec, [Mode | Rest]) -> - try diameter_codegen:from_spec(File, Spec, Opts, Mode) of - ok -> - make(File, Opts, Spec, Rest) +make(File, Opts, Dict, [Mode | Rest]) -> + try + ok = diameter_codegen:from_dict(File, Dict, Opts, Mode), + make(File, Opts, Dict, Rest) catch error: Reason -> - {error, {Reason, Mode, erlang:get_stacktrace()}} + erlang:error({Reason, Mode, erlang:get_stacktrace()}) end. diff --git a/lib/diameter/src/compiler/diameter_nowarn.erl b/lib/diameter/src/compiler/diameter_nowarn.erl new file mode 100644 index 0000000000..6c17af6563 --- /dev/null +++ b/lib/diameter/src/compiler/diameter_nowarn.erl @@ -0,0 +1,41 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% A parse transform to work around dialyzer currently not +%% understanding nowarn_unused_function except on individual +%% functions. The include of diameter_gen.hrl by generated dictionary +%% modules contains code that may not be called depending on the +%% dictionary. (The relay dictionary for example.) +%% +%% Even called functions may contain cases that aren't used for a +%% particular dictionary. This also causes dialyzer to complain but +%% there's no way to silence it in this case. +%% + +-module(diameter_nowarn). + +-export([parse_transform/2]). + +parse_transform(Forms, _Options) -> + [{attribute, ?LINE, compile, {nowarn_unused_function, {F,A}}} + || {function, _, F, A, _} <- Forms] + ++ Forms. +%% Note that dialyzer also doesn't understand {nowarn_unused_function, FAs} +%% with FAs a list of tuples. diff --git a/lib/diameter/src/compiler/diameter_spec_scan.erl b/lib/diameter/src/compiler/diameter_spec_scan.erl deleted file mode 100644 index bc0448882a..0000000000 --- a/lib/diameter/src/compiler/diameter_spec_scan.erl +++ /dev/null @@ -1,157 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - --module(diameter_spec_scan). - -%% -%% Functions used by the spec file parser in diameter_spec_util. -%% - --export([split/1, - split/2, - parse/1]). - -%%% ----------------------------------------------------------- -%%% # parse/1 -%%% -%%% Output: list of Token -%%% -%%% Token = '{' | '}' | '<' | '>' | '[' | ']' -%%% | '*' | '::=' | ':' | ',' | '-' -%%% | {name, string()} -%%% | {tag, atom()} -%%% | {number, integer() >= 0} -%%% -%%% Tokenize a string. Fails if the string does not parse. -%%% ----------------------------------------------------------- - -parse(S) -> - parse(S, []). - -%% parse/2 - -parse(S, Acc) -> - acc(split(S), Acc). - -acc({T, Rest}, Acc) -> - parse(Rest, [T | Acc]); -acc("", Acc) -> - lists:reverse(Acc). - -%%% ----------------------------------------------------------- -%%% # split/2 -%%% -%%% Output: {list() of Token, Rest} -%%% -%%% Extract a specified number of tokens from a string. Returns a list -%%% of length less than the specified number if there are less than -%%% this number of tokens to be parsed. -%%% ----------------------------------------------------------- - -split(Str, N) - when N >= 0 -> - split(N, Str, []). - -split(0, Str, Acc) -> - {lists:reverse(Acc), Str}; - -split(N, Str, Acc) -> - case split(Str) of - {T, Rest} -> - split(N-1, Rest, [T|Acc]); - "" = Rest -> - {lists:reverse(Acc), Rest} - end. - -%%% ----------------------------------------------------------- -%%% # split/1 -%%% -%%% Output: {Token, Rest} | "" -%%% -%%% Extract the next token from a string. -%%% ----------------------------------------------------------- - -split("" = Rest) -> - Rest; - -split("::=" ++ T) -> - {'::=', T}; - -split([H|T]) - when H == ${; H == $}; - H == $<; H == $>; - H == $[; H == $]; - H == $*; H == $:; H == $,; H == $- -> - {list_to_atom([H]), T}; - -split([H|T]) when $A =< H, H =< $Z; - $0 =< H, H =< $9 -> - {P, Rest} = splitwith(fun is_name_ch/1, [H], T), - Tok = try - {number, read_int(P)} - catch - error:_ -> - {name, P} - end, - {Tok, Rest}; - -split([H|T]) when $a =< H, H =< $z -> - {P, Rest} = splitwith(fun is_name_ch/1, [H], T), - {{tag, list_to_atom(P)}, Rest}; - -split([H|T]) when H == $\t; - H == $\s; - H == $\n -> - split(T). - -%% read_int/1 - -read_int([$0,X|S]) - when X == $X; - X == $x -> - {ok, [N], []} = io_lib:fread("~16u", S), - N; - -read_int(S) -> - list_to_integer(S). - -%% splitwith/3 - -splitwith(Fun, Acc, S) -> - split([] /= S andalso Fun(hd(S)), Fun, Acc, S). - -split(true, Fun, Acc, [H|T]) -> - splitwith(Fun, [H|Acc], T); -split(false, _, Acc, S) -> - {lists:reverse(Acc), S}. - -is_name_ch(C) -> - is_alphanum(C) orelse C == $- orelse C == $_. - -is_alphanum(C) -> - is_lower(C) orelse is_upper(C) orelse is_digit(C). - -is_lower(C) -> - $a =< C andalso C =< $z. - -is_upper(C) -> - $A =< C andalso C =< $Z. - -is_digit(C) -> - $0 =< C andalso C =< $9. diff --git a/lib/diameter/src/compiler/diameter_spec_util.erl b/lib/diameter/src/compiler/diameter_spec_util.erl deleted file mode 100644 index 62536bf06d..0000000000 --- a/lib/diameter/src/compiler/diameter_spec_util.erl +++ /dev/null @@ -1,1089 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2011. 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% -%% - -%% -%% This module turns a .dia (aka spec) file into the orddict that -%% diameter_codegen.erl in turn morphs into .erl and .hrl files for -%% encode and decode of Diameter messages and AVPs. -%% - --module(diameter_spec_util). - --export([parse/2]). - --define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})). --define(ATOM, list_to_atom). - -%% parse/1 -%% -%% Output: orddict() - -parse(Path, Opts) -> - put({?MODULE, verbose}, lists:member(verbose, Opts)), - {ok, B} = file:read_file(Path), - Chunks = chunk(B), - Spec = reset(make_spec(Chunks), Opts, [name, prefix, inherits]), - true = groups_defined(Spec), %% sanity checks - true = customs_defined(Spec), %% - Full = import_enums(import_groups(import_avps(insert_codes(Spec), Opts))), - true = enums_defined(Full), %% sanity checks - true = v_flags_set(Spec), - Full. - -reset(Spec, Opts, Keys) -> - lists:foldl(fun(K,S) -> - reset([{A,?ATOM(V)} || {A,V} <- Opts, A == K], S) - end, - Spec, - Keys). - -reset(L, Spec) - when is_list(L) -> - lists:foldl(fun reset/2, Spec, L); - -reset({inherits = Key, '-'}, Spec) -> - orddict:erase(Key, Spec); -reset({inherits = Key, Dict}, Spec) -> - orddict:append(Key, Dict, Spec); -reset({Key, Atom}, Spec) -> - orddict:store(Key, Atom, Spec); -reset(_, Spec) -> - Spec. - -%% Optional reports when running verbosely. -report(What, Data) -> - report(get({?MODULE, verbose}), What, Data). - -report(true, Tag, Data) -> - io:format("##~n## ~p ~p~n", [Tag, Data]); -report(false, _, _) -> - ok. - -%% chunk/1 - -chunk(B) -> - chunkify(normalize(binary_to_list(B))). - -%% normalize/1 -%% -%% Replace CR NL by NL, multiple NL by one, tab by space, and strip -%% comments and leading/trailing space from each line. Precludes -%% semicolons being used for any other purpose than comments. - -normalize(Str) -> - nh(Str, []). - -nh([], Acc) -> - lists:reverse(Acc); - -%% Trim leading whitespace. -nh(Str, Acc) -> - nb(trim(Str), Acc). - -%% tab -> space -nb([$\t|Rest], Acc) -> - nb(Rest, [$\s|Acc]); - -%% CR NL -> NL -nb([$\r,$\n|Rest], Acc) -> - nt(Rest, Acc); - -%% Gobble multiple newlines before starting over again. -nb([$\n|Rest], Acc) -> - nt(Rest, Acc); - -%% Comment. -nb([$;|Rest], Acc) -> - nb(lists:dropwhile(fun(C) -> C /= $\n end, Rest), Acc); - -%% Just an ordinary character. Boring ... -nb([C|Rest], Acc) -> - nb(Rest, [C|Acc]); - -nb([] = Str, Acc) -> - nt(Str, Acc). - -%% Discard a subsequent newline. -nt(T, [$\n|_] = Acc) -> - nh(T, trim(Acc)); - -%% Trim whitespace from the end of the line before continuing. -nt(T, Acc) -> - nh(T, [$\n|trim(Acc)]). - -trim(S) -> - lists:dropwhile(fun(C) -> lists:member(C, "\s\t") end, S). - -%% chunkify/1 -%% -%% Split the spec file into pieces delimited by lines starting with -%% @Tag. Returns a list of {Tag, Args, Chunk} where Chunk is the -%% string extending to the next delimiter. Note that leading -%% whitespace has already been stripped. - -chunkify(Str) -> - %% Drop characters to the start of the first chunk. - {_, Rest} = split_chunk([$\n|Str]), - chunkify(Rest, []). - -chunkify([], Acc) -> - lists:reverse(Acc); - -chunkify(Rest, Acc) -> - {H,T} = split_chunk(Rest), - chunkify(T, [split_tag(H) | Acc]). - -split_chunk(Str) -> - split_chunk(Str, []). - -split_chunk([] = Rest, Acc) -> - {lists:reverse(Acc), Rest}; -split_chunk([$@|Rest], [$\n|_] = Acc) -> - {lists:reverse(Acc), Rest}; -split_chunk([C|Rest], Acc) -> - split_chunk(Rest, [C|Acc]). - -%% Expect a tag and its arguments on a single line. -split_tag(Str) -> - {L, Rest} = get_until($\n, Str), - [{tag, Tag} | Toks] = diameter_spec_scan:parse(L), - {Tag, Toks, trim(Rest)}. - -get_until(EndT, L) -> - {H, [EndT | T]} = lists:splitwith(fun(C) -> C =/= EndT end, L), - {H,T}. - -%% ------------------------------------------------------------------------ -%% make_spec/1 -%% -%% Turn chunks into spec. - -make_spec(Chunks) -> - lists:foldl(fun(T,A) -> report(chunk, T), chunk(T,A) end, - orddict:new(), - Chunks). - -chunk({T, [X], []}, Dict) - when T == name; - T == prefix -> - store(T, atomize(X), Dict); - -chunk({id = T, [{number, I}], []}, Dict) -> - store(T, I, Dict); - -chunk({vendor = T, [{number, I}, N], []}, Dict) -> - store(T, {I, atomize(N)}, Dict); - -%% inherits -> [{Mod, [AvpName, ...]}, ...] -chunk({inherits = T, [_,_|_] = Args, []}, Acc) -> - Mods = [atomize(A) || A <- Args], - append_list(T, [{M,[]} || M <- Mods], Acc); -chunk({inherits = T, [Mod], Body}, Acc) -> - append(T, {atomize(Mod), parse_avp_names(Body)}, Acc); - -%% avp_types -> [{AvpName, Code, Type, Flags, Encr}, ...] -chunk({avp_types = T, [], Body}, Acc) -> - store(T, parse_avp_types(Body), Acc); - -%% custom_types -> [{Mod, [AvpName, ...]}, ...] -chunk({custom_types = T, [Mod], Body}, Dict) -> - [_|_] = Avps = parse_avp_names(Body), - append(T, {atomize(Mod), Avps}, Dict); - -%% messages -> [{MsgName, Code, Type, Appl, Avps}, ...] -chunk({messages = T, [], Body}, Acc) -> - store(T, parse_messages(Body), Acc); - -%% grouped -> [{AvpName, Code, Vendor, Avps}, ...] -chunk({grouped = T, [], Body}, Acc) -> - store(T, parse_groups(Body), Acc); - -%% avp_vendor_id -> [{Id, [AvpName, ...]}, ...] -chunk({avp_vendor_id = T, [{number, I}], Body}, Dict) -> - [_|_] = Names = parse_avp_names(Body), - append(T, {I, Names}, Dict); - -%% enums -> [{AvpName, [{Value, Name}, ...]}, ...] -chunk({enum, [N], Str}, Dict) -> - append(enums, {atomize(N), parse_enums(Str)}, Dict); - -%% defines -> [{DefineName, [{Value, Name}, ...]}, ...] -chunk({define, [N], Str}, Dict) -> - append(defines, {atomize(N), parse_enums(Str)}, Dict); -chunk({result_code, [_] = N, Str}, Dict) -> %% backwards compatibility - chunk({define, N, Str}, Dict); - -%% commands -> [{Name, Abbrev}, ...] -chunk({commands = T, [], Body}, Dict) -> - store(T, parse_commands(Body), Dict); - -chunk(T, _) -> - ?ERROR({unknown_tag, T}). - -store(Key, Value, Dict) -> - error == orddict:find(Key, Dict) orelse ?ERROR({duplicate, Key}), - orddict:store(Key, Value, Dict). -append(Key, Value, Dict) -> - orddict:append(Key, Value, Dict). -append_list(Key, Values, Dict) -> - orddict:append_list(Key, Values, Dict). - -atomize({tag, T}) -> - T; -atomize({name, T}) -> - ?ATOM(T). - -get_value(Keys, Spec) - when is_list(Keys) -> - [get_value(K, Spec) || K <- Keys]; -get_value(Key, Spec) -> - proplists:get_value(Key, Spec, []). - -%% ------------------------------------------------------------------------ -%% enums_defined/1 -%% groups_defined/1 -%% customs_defined/1 -%% -%% Ensure that every local enum/grouped/custom is defined as an avp -%% with an appropriate type. - -enums_defined(Spec) -> - Avps = get_value(avp_types, Spec), - Import = get_value(import_enums, Spec), - lists:all(fun({N,_}) -> - true = enum_defined(N, Avps, Import) - end, - get_value(enums, Spec)). - -enum_defined(Name, Avps, Import) -> - case lists:keyfind(Name, 1, Avps) of - {Name, _, 'Enumerated', _, _} -> - true; - {Name, _, T, _, _} -> - ?ERROR({avp_has_wrong_type, Name, 'Enumerated', T}); - false -> - lists:any(fun({_,Is}) -> lists:keymember(Name, 1, Is) end, Import) - orelse ?ERROR({avp_not_defined, Name, 'Enumerated'}) - end. -%% Note that an AVP is imported only if referenced by a message or -%% grouped AVP, so the final branch will fail if an enum definition is -%% extended without this being the case. - -groups_defined(Spec) -> - Avps = get_value(avp_types, Spec), - lists:all(fun({N,_,_,_}) -> true = group_defined(N, Avps) end, - get_value(grouped, Spec)). - -group_defined(Name, Avps) -> - case lists:keyfind(Name, 1, Avps) of - {Name, _, 'Grouped', _, _} -> - true; - {Name, _, T, _, _} -> - ?ERROR({avp_has_wrong_type, Name, 'Grouped', T}); - false -> - ?ERROR({avp_not_defined, Name, 'Grouped'}) - end. - -customs_defined(Spec) -> - Avps = get_value(avp_types, Spec), - lists:all(fun(A) -> true = custom_defined(A, Avps) end, - lists:flatmap(fun last/1, get_value(custom_types, Spec))). - -custom_defined(Name, Avps) -> - case lists:keyfind(Name, 1, Avps) of - {Name, _, T, _, _} when T == 'Grouped'; - T == 'Enumerated' -> - ?ERROR({avp_has_invalid_custom_type, Name, T}); - {Name, _, _, _, _} -> - true; - false -> - ?ERROR({avp_not_defined, Name}) - end. - -last({_,Xs}) -> Xs. - -%% ------------------------------------------------------------------------ -%% v_flags_set/1 - -v_flags_set(Spec) -> - Avps = get_value(avp_types, Spec) - ++ lists:flatmap(fun last/1, get_value(import_avps, Spec)), - Vs = lists:flatmap(fun last/1, get_value(avp_vendor_id, Spec)), - - lists:all(fun(N) -> vset(N, Avps) end, Vs). - -vset(Name, Avps) -> - A = lists:keyfind(Name, 1, Avps), - false == A andalso ?ERROR({avp_not_defined, Name}), - {Name, _Code, _Type, Flags, _Encr} = A, - lists:member('V', Flags) orelse ?ERROR({v_flag_not_set, A}). - -%% ------------------------------------------------------------------------ -%% insert_codes/1 - -insert_codes(Spec) -> - [Msgs, Cmds] = get_value([messages, commands], Spec), - - %% Code -> [{Name, Flags}, ...] - Dict = lists:foldl(fun({N,C,Fs,_,_}, D) -> dict:append(C,{N,Fs},D) end, - dict:new(), - Msgs), - - %% list() of {Code, {ReqName, ReqAbbr}, {AnsName, AnsAbbr}} - %% If the name and abbreviation are the same then the 2-tuples - %% are replaced by the common atom()-valued name. - Codes = dict:fold(fun(C,Ns,A) -> [make_code(C, Ns, Cmds) | A] end, - [], - dict:erase(-1, Dict)), %% answer-message - - orddict:store(command_codes, Codes, Spec). - -make_code(Code, [_,_] = Ns, Cmds) -> - {Req, Ans} = make_names(Ns, lists:map(fun({_,Fs}) -> - lists:member('REQ', Fs) - end, - Ns)), - {Code, abbrev(Req, Cmds), abbrev(Ans, Cmds)}; - -make_code(Code, Cs, _) -> - ?ERROR({missing_request_or_answer, Code, Cs}). - -%% 3.3. Diameter Command Naming Conventions -%% -%% Diameter command names typically includes one or more English words -%% followed by the verb Request or Answer. Each English word is -%% delimited by a hyphen. A three-letter acronym for both the request -%% and answer is also normally provided. - -make_names([{Rname,_},{Aname,_}], [true, false]) -> - {Rname, Aname}; -make_names([{Aname,_},{Rname,_}], [false, true]) -> - {Rname, Aname}; -make_names([_,_] = Names, _) -> - ?ERROR({inconsistent_command_flags, Names}). - -abbrev(Name, Cmds) -> - case abbr(Name, get_value(Name, Cmds)) of - Name -> - Name; - Abbr -> - {Name, Abbr} - end. - -%% No explicit abbreviation: construct. -abbr(Name, []) -> - ?ATOM(abbr(string:tokens(atom_to_list(Name), "-"))); - -%% Abbreviation was specified. -abbr(_Name, Abbr) -> - Abbr. - -%% No hyphens: already abbreviated. -abbr([Abbr]) -> - Abbr; - -%% XX-Request/Answer ==> XXR/XXA -abbr([[_,_] = P, T]) - when T == "Request"; - T == "Answer" -> - P ++ [hd(T)]; - -%% XXX-...-YYY-Request/Answer ==> X...YR/X...YA -abbr([_,_|_] = L) -> - lists:map(fun erlang:hd/1, L). - -%% ------------------------------------------------------------------------ -%% import_avps/2 - -import_avps(Spec, Options) -> - Msgs = get_value(messages, Spec), - Groups = get_value(grouped, Spec), - - %% Messages and groups require AVP's referenced by them. - NeededAvps - = ordsets:from_list(lists:flatmap(fun({_,_,_,_,As}) -> - [avp_name(A) || A <- As] - end, - Msgs) - ++ lists:flatmap(fun({_,_,_,As}) -> - [avp_name(A) || A <- As] - end, - Groups)), - MissingAvps = missing_avps(NeededAvps, Spec), - - report(needed, NeededAvps), - report(missing, MissingAvps), - - Import = inherit(get_value(inherits, Spec), Options), - - report(imported, Import), - - ImportedAvps = lists:map(fun({N,_,_,_,_}) -> N end, - lists:flatmap(fun last/1, Import)), - - Unknown = MissingAvps -- ImportedAvps, - - [] == Unknown orelse ?ERROR({undefined_avps, Unknown}), - - orddict:store(import_avps, Import, orddict:erase(inherits, Spec)). - -%% missing_avps/2 -%% -%% Given a list of AVP names and parsed spec, return the list of -%% AVP's that aren't defined in this spec. - -missing_avps(NeededNames, Spec) -> - Avps = get_value(avp_types, Spec), - Groups = lists:map(fun({N,_,_,As}) -> - {N, [avp_name(A) || A <- As]} - end, - get_value(grouped, Spec)), - Names = ordsets:from_list(['AVP' | lists:map(fun({N,_,_,_,_}) -> N end, - Avps)]), - missing_avps(NeededNames, [], {Names, Groups}). - -avp_name({'<',A,'>'}) -> A; -avp_name({A}) -> A; -avp_name([A]) -> A; -avp_name({_, A}) -> avp_name(A). - -missing_avps(NeededNames, MissingNames, {Names, _} = T) -> - missing(ordsets:filter(fun(N) -> lists:member(N, NeededNames) end, Names), - ordsets:union(NeededNames, MissingNames), - T). - -%% Nothing found locally. -missing([], MissingNames, _) -> - MissingNames; - -%% Or not. Keep looking for for the AVP's needed by the found AVP's of -%% type Grouped. -missing(FoundNames, MissingNames, {_, Groups} = T) -> - NeededNames = lists:flatmap(fun({N,As}) -> - choose(lists:member(N, FoundNames), As, []) - end, - Groups), - missing_avps(ordsets:from_list(NeededNames), - ordsets:subtract(MissingNames, FoundNames), - T). - -%% inherit/2 - -inherit(Inherits, Options) -> - Dirs = [D || {include, D} <- Options] ++ ["."], - lists:foldl(fun(T,A) -> find_avps(T, A, Dirs) end, [], Inherits). - -find_avps({Mod, AvpNames}, Acc, Path) -> - report(inherit_from, Mod), - Avps = avps_from_beam(find_beam(Mod, Path), Mod), %% could be empty - [{Mod, lists:sort(find_avps(AvpNames, Avps))} | Acc]. - -find_avps([], Avps) -> - Avps; -find_avps(Names, Avps) -> - lists:filter(fun({N,_,_,_,_}) -> lists:member(N, Names) end, Avps). - -%% find_beam/2 - -find_beam(Mod, Dirs) - when is_atom(Mod) -> - find_beam(atom_to_list(Mod), Dirs); -find_beam(Mod, Dirs) -> - Beam = Mod ++ code:objfile_extension(), - case try_path(Dirs, Beam) of - {value, Path} -> - Path; - false -> - ?ERROR({beam_not_on_path, Beam, Dirs}) - end. - -try_path([D|Ds], Fname) -> - Path = filename:join(D, Fname), - case file:read_file_info(Path) of - {ok, _} -> - {value, Path}; - _ -> - try_path(Ds, Fname) - end; -try_path([], _) -> - false. - -%% avps_from_beam/2 - -avps_from_beam(Path, Mod) -> - report(beam, Path), - ok = load_module(code:is_loaded(Mod), Mod, Path), - orddict:fetch(avp_types, Mod:dict()). - -load_module(false, Mod, Path) -> - R = filename:rootname(Path, code:objfile_extension()), - {module, Mod} = code:load_abs(R), - ok; -load_module({file, _}, _, _) -> - ok. - -choose(true, X, _) -> X; -choose(false, _, X) -> X. - -%% ------------------------------------------------------------------------ -%% import_groups/1 -%% import_enums/1 -%% -%% For each inherited module, store the content of imported AVP's of -%% type grouped/enumerated in a new key. - -import_groups(Spec) -> - orddict:store(import_groups, import(grouped, Spec), Spec). - -import_enums(Spec) -> - orddict:store(import_enums, import(enums, Spec), Spec). - -import(Key, Spec) -> - lists:flatmap(fun(T) -> import_key(Key, T) end, - get_value(import_avps, Spec)). - -import_key(Key, {Mod, Avps}) -> - Imports = lists:flatmap(fun(T) -> - choose(lists:keymember(element(1,T), - 1, - Avps), - [T], - []) - end, - get_value(Key, Mod:dict())), - if Imports == [] -> - []; - true -> - [{Mod, Imports}] - end. - -%% ------------------------------------------------------------------------ -%% parse_enums/1 -%% -%% Enums are specified either as the integer value followed by the -%% name or vice-versa. In the former case the name of the enum is -%% taken to be the string up to the end of line, which may contain -%% whitespace. In the latter case the integer may be parenthesized, -%% specified in hex and followed by an inline comment. This is -%% historical and will likely be changed to require a precise input -%% format. -%% -%% Output: list() of {integer(), atom()} - -parse_enums(Str) -> - lists:flatmap(fun(L) -> parse_enum(trim(L)) end, string:tokens(Str, "\n")). - -parse_enum([]) -> - []; - -parse_enum(Str) -> - REs = [{"^(0[xX][0-9A-Fa-f]+|[0-9]+)\s+(.*?)\s*$", 1, 2}, - {"^(.+?)\s+(0[xX][0-9A-Fa-f]+|[0-9]+)(\s+.*)?$", 2, 1}, - {"^(.+?)\s+\\((0[xX][0-9A-Fa-f]+|[0-9]+)\\)(\s+.*)?$", 2, 1}], - parse_enum(Str, REs). - -parse_enum(Str, REs) -> - try lists:foreach(fun(R) -> enum(Str, R) end, REs) of - ok -> - ?ERROR({bad_enum, Str}) - catch - throw: {enum, T} -> - [T] - end. - -enum(Str, {Re, I, N}) -> - case re:run(Str, Re, [{capture, all_but_first, list}]) of - {match, Vs} -> - T = list_to_tuple(Vs), - throw({enum, {to_int(element(I,T)), ?ATOM(element(N,T))}}); - nomatch -> - ok - end. - -to_int([$0,X|Hex]) - when X == $x; - X == $X -> - {ok, [I], _} = io_lib:fread("~#", "16#" ++ Hex), - I; -to_int(I) -> - list_to_integer(I). - -%% ------------------------------------------------------------------------ -%% parse_messages/1 -%% -%% Parse according to the ABNF for message specifications in 3.2 of -%% RFC 3588 (shown below). We require all message and AVP names to -%% start with a digit or uppercase character, except for the base -%% answer-message, which is treated as a special case. Allowing names -%% that start with a digit is more than the RFC specifies but the name -%% doesn't affect what's sent over the wire. (Certains 3GPP standards -%% use names starting with a digit. eg 3GPP-Charging-Id in TS32.299.) - -%% -%% Sadly, not even the RFC follows this grammar. In particular, except -%% in the example in 3.2, it wraps each command-name in angle brackets -%% ('<' '>') which makes parsing a sequence of specifications require -%% lookahead: after 'optional' avps have been parsed, it's not clear -%% whether a '<' is a 'fixed' or whether it's the start of a -%% subsequent message until we see whether or not '::=' follows the -%% closing '>'. Require the grammar as specified. -%% -%% Output: list of {Name, Code, Flags, ApplId, Avps} -%% -%% Name = atom() -%% Code = integer() -%% Flags = integer() -%% ApplId = [] | [integer()] -%% Avps = see parse_avps/1 - -parse_messages(Str) -> - p_cmd(trim(Str), []). - -%% command-def = command-name "::=" diameter-message -%% -%% command-name = diameter-name -%% -%% diameter-name = ALPHA *(ALPHA / DIGIT / "-") -%% -%% diameter-message = header [ *fixed] [ *required] [ *optional] -%% [ *fixed] -%% -%% header = "<" Diameter-Header:" command-id -%% [r-bit] [p-bit] [e-bit] [application-id]">" -%% -%% The header spec (and example that follows it) is slightly mangled -%% and, given the examples in the RFC should as follows: -%% -%% header = "<" "Diameter Header:" command-id -%% [r-bit] [p-bit] [e-bit] [application-id]">" -%% -%% This is what's required/parsed below, modulo whitespace. This is -%% also what's specified in the current draft standard at -%% http://ftp.ietf.org/drafts/wg/dime. -%% -%% Note that the grammar specifies the order fixed, required, -%% optional. In practise there seems to be little difference between -%% the latter two since qualifiers can be used to change the -%% semantics. For example 1*[XXX] and *1{YYY} specify 1 or more of the -%% optional avp XXX and 0 or 1 of the required avp YYY, making the -%% iotional avp required and the required avp optional. The current -%% draft addresses this somewhat by requiring that min for a qualifier -%% on an optional avp must be 0 if present. It doesn't say anything -%% about required avps however, so specifying a min of 0 would still -%% be possible. The draft also does away with the trailing *fixed. -%% -%% What will be parsed here will treat required and optional -%% interchangeably. That is. only require that required/optional -%% follow and preceed fixed, not that optional avps must follow -%% required ones. We already have several specs for which this parsing -%% is necessary and there seems to be no harm in accepting it. - -p_cmd("", Acc) -> - lists:reverse(Acc); - -p_cmd(Str, Acc) -> - {Next, Rest} = split_def(Str), - report(command, Next), - p_cmd(Rest, [p_cmd(Next) | Acc]). - -p_cmd("answer-message" ++ Str) -> - p_header([{name, 'answer-message'} | diameter_spec_scan:parse(Str)]); - -p_cmd(Str) -> - p_header(diameter_spec_scan:parse(Str)). - -%% p_header/1 - -p_header(['<', {name, _} = N, '>' | Toks]) -> - p_header([N | Toks]); - -p_header([{name, 'answer-message' = N}, '::=', - '<', {name, "Diameter"}, {name, "Header"}, ':', {tag, code}, - ',', {name, "ERR"}, '[', {name, "PXY"}, ']', '>' - | Toks]) -> - {N, -1, ['ERR', 'PXY'], [], parse_avps(Toks)}; - -p_header([{name, Name}, '::=', - '<', {name, "Diameter"}, {name, "Header"}, ':', {number, Code} - | Toks]) -> - {Flags, Rest} = p_flags(Toks), - {ApplId, [C|_] = R} = p_appl(Rest), - '>' == C orelse ?ERROR({invalid_flag, {Name, Code, Flags, ApplId}, R}), - {?ATOM(Name), Code, Flags, ApplId, parse_avps(tl(R))}; - -p_header(Toks) -> - ?ERROR({invalid_header, Toks}). - -%% application-id = 1*DIGIT -%% -%% command-id = 1*DIGIT -%% ; The Command Code assigned to the command -%% -%% r-bit = ", REQ" -%% ; If present, the 'R' bit in the Command -%% ; Flags is set, indicating that the message -%% ; is a request, as opposed to an answer. -%% -%% p-bit = ", PXY" -%% ; If present, the 'P' bit in the Command -%% ; Flags is set, indicating that the message -%% ; is proxiable. -%% -%% e-bit = ", ERR" -%% ; If present, the 'E' bit in the Command -%% ; Flags is set, indicating that the answer -%% ; message contains a Result-Code AVP in -%% ; the "protocol error" class. - -p_flags(Toks) -> - lists:foldl(fun p_flags/2, {[], Toks}, ["REQ", "PXY", "ERR"]). - -p_flags(N, {Acc, [',', {name, N} | Toks]}) -> - {[?ATOM(N) | Acc], Toks}; - -p_flags(_, T) -> - T. - -%% The RFC doesn't specify ',' before application-id but this seems a -%% bit inconsistent. Accept a comma if it exists. -p_appl([',', {number, I} | Toks]) -> - {[I], Toks}; -p_appl([{number, I} | Toks]) -> - {[I], Toks}; -p_appl(Toks) -> - {[], Toks}. - -%% parse_avps/1 -%% -%% Output: list() of Avp | {Qual, Avp} -%% -%% Qual = '*' | {Min, '*'} | {'*', Max} | {Min, Max} -%% Avp = {'<', Name, '>'} | {Name} | [Name] -%% -%% Min, Max = integer() >= 0 - -parse_avps(Toks) -> - p_avps(Toks, ['<', '|', '<'], []). -%% The list corresponds to the delimiters expected at the front, middle -%% and back of the avp specification, '|' representing '{' and '['. - -%% fixed = [qual] "<" avp-spec ">" -%% ; Defines the fixed position of an AVP -%% -%% required = [qual] "{" avp-spec "}" -%% ; The AVP MUST be present and can appear -%% ; anywhere in the message. -%% -%% optional = [qual] "[" avp-name "]" -%% ; The avp-name in the 'optional' rule cannot -%% ; evaluate to any AVP Name which is included -%% ; in a fixed or required rule. The AVP can -%% ; appear anywhere in the message. -%% -%% qual = [min] "*" [max] -%% ; See ABNF conventions, RFC 2234 Section 6.6. -%% ; The absence of any qualifiers depends on whether -%% ; it precedes a fixed, required, or optional -%% ; rule. If a fixed or required rule has no -%% ; qualifier, then exactly one such AVP MUST -%% ; be present. If an optional rule has no -%% ; qualifier, then 0 or 1 such AVP may be -%% ; present. -%% ; -%% ; NOTE: "[" and "]" have a different meaning -%% ; than in ABNF (see the optional rule, above). -%% ; These braces cannot be used to express -%% ; optional fixed rules (such as an optional -%% ; ICV at the end). To do this, the convention -%% ; is '0*1fixed'. -%% -%% min = 1*DIGIT -%% ; The minimum number of times the element may -%% ; be present. The default value is zero. -%% -%% max = 1*DIGIT -%% ; The maximum number of times the element may -%% ; be present. The default value is infinity. A -%% ; value of zero implies the AVP MUST NOT be -%% ; present. -%% -%% avp-spec = diameter-name -%% ; The avp-spec has to be an AVP Name, defined -%% ; in the base or extended Diameter -%% ; specifications. -%% -%% avp-name = avp-spec / "AVP" -%% ; The string "AVP" stands for *any* arbitrary -%% ; AVP Name, which does not conflict with the -%% ; required or fixed position AVPs defined in -%% ; the command code definition. -%% - -p_avps([], _, Acc) -> - lists:reverse(Acc); - -p_avps(Toks, Delim, Acc) -> - {Qual, Rest} = p_qual(Toks), - {Avp, R, D} = p_avp(Rest, Delim), - T = if Qual == false -> - Avp; - true -> - {Qual, Avp} - end, - p_avps(R, D, [T | Acc]). - -p_qual([{number, Min}, '*', {number, Max} | Toks]) -> - {{Min, Max}, Toks}; -p_qual([{number, Min}, '*' = Max | Toks]) -> - {{Min, Max}, Toks}; -p_qual(['*' = Min, {number, Max} | Toks]) -> - {{Min, Max}, Toks}; -p_qual(['*' = Q | Toks]) -> - {Q, Toks}; -p_qual(Toks) -> - {false, Toks}. - -p_avp([B, {name, Name}, E | Toks], [_|_] = Delim) -> - {avp(B, ?ATOM(Name), E), - Toks, - delim(choose(B == '<', B, '|'), Delim)}; -p_avp(Toks, Delim) -> - ?ERROR({invalid_avp, Toks, Delim}). - -avp('<' = B, Name, '>' = E) -> - {B, Name, E}; -avp('{', Name, '}') -> - {Name}; -avp('[', Name, ']') -> - [Name]; -avp(B, Name, E) -> - ?ERROR({invalid_avp, B, Name, E}). - -delim(B, D) -> - if B == hd(D) -> D; true -> tl(D) end. - -%% split_def/1 -%% -%% Strip one command definition off head of a string. - -split_def(Str) -> - sdh(Str, []). - -%% Look for the "::=" starting off the definition. -sdh("", _) -> - ?ERROR({missing, '::='}); -sdh("::=" ++ Rest, Acc) -> - sdb(Rest, [$=,$:,$:|Acc]); -sdh([C|Rest], Acc) -> - sdh(Rest, [C|Acc]). - -%% Look for the "::=" starting off the following definition. -sdb("::=" ++ _ = Rest, Acc) -> - sdt(trim(Acc), Rest); -sdb("" = Rest, Acc) -> - sd(Acc, Rest); -sdb([C|Rest], Acc) -> - sdb(Rest, [C|Acc]). - -%% Put name characters of the subsequent specification back into Rest. -sdt([C|Acc], Rest) - when C /= $\n, C /= $\s -> - sdt(Acc, [C|Rest]); - -sdt(Acc, Rest) -> - sd(Acc, Rest). - -sd(Acc, Rest) -> - {trim(lists:reverse(Acc)), Rest}. -%% Note that Rest is already trimmed of leading space. - -%% ------------------------------------------------------------------------ -%% parse_groups/1 -%% -%% Parse according to the ABNF for message specifications in 4.4 of -%% RFC 3588 (shown below). Again, allow names starting with a digit -%% and also require "AVP Header" without "-" since this is what -%% the RFC uses in all examples. -%% -%% Output: list of {Name, Code, Vendor, Avps} -%% -%% Name = atom() -%% Code = integer() -%% Vendor = [] | [integer()] -%% Avps = see parse_avps/1 - -parse_groups(Str) -> - p_group(trim(Str), []). - -%% grouped-avp-def = name "::=" avp -%% -%% name-fmt = ALPHA *(ALPHA / DIGIT / "-") -%% -%% name = name-fmt -%% ; The name has to be the name of an AVP, -%% ; defined in the base or extended Diameter -%% ; specifications. -%% -%% avp = header [ *fixed] [ *required] [ *optional] -%% [ *fixed] -%% -%% header = "<" "AVP-Header:" avpcode [vendor] ">" -%% -%% avpcode = 1*DIGIT -%% ; The AVP Code assigned to the Grouped AVP -%% -%% vendor = 1*DIGIT -%% ; The Vendor-ID assigned to the Grouped AVP. -%% ; If absent, the default value of zero is -%% ; used. - -p_group("", Acc) -> - lists:reverse(Acc); - -p_group(Str, Acc) -> - {Next, Rest} = split_def(Str), - report(group, Next), - p_group(Rest, [p_group(diameter_spec_scan:parse(Next)) | Acc]). - -p_group([{name, Name}, '::=', '<', {name, "AVP"}, {name, "Header"}, - ':', {number, Code} - | Toks]) -> - {Id, [C|_] = R} = p_vendor(Toks), - C == '>' orelse ?ERROR({invalid_group_header, R}), - {?ATOM(Name), Code, Id, parse_avps(tl(R))}; - -p_group(Toks) -> - ?ERROR({invalid_group, Toks}). - -p_vendor([{number, I} | Toks]) -> - {[I], Toks}; -p_vendor(Toks) -> - {[], Toks}. - -%% ------------------------------------------------------------------------ -%% parse_avp_names/1 - -parse_avp_names(Str) -> - [p_name(N) || N <- diameter_spec_scan:parse(Str)]. - -p_name({name, N}) -> - ?ATOM(N); -p_name(T) -> - ?ERROR({invalid_avp_name, T}). - -%% ------------------------------------------------------------------------ -%% parse_avp_types/1 -%% -%% Output: list() of {Name, Code, Type, Flags, Encr} - -parse_avp_types(Str) -> - p_avp_types(Str, []). - -p_avp_types(Str, Acc) -> - p_type(diameter_spec_scan:split(Str, 3), Acc). - -p_type({[],[]}, Acc) -> - lists:reverse(Acc); - -p_type({[{name, Name}, {number, Code}, {name, Type}], Str}, Acc) -> - {Flags, Encr, Rest} = try - p_avp_flags(trim(Str), []) - catch - throw: {?MODULE, Reason} -> - ?ERROR({invalid_avp_type, Reason}) - end, - p_avp_types(Rest, [{?ATOM(Name), Code, ?ATOM(type(Type)), Flags, Encr} - | Acc]); - -p_type(T, _) -> - ?ERROR({invalid_avp_type, T}). - -p_avp_flags([C|Str], Acc) - when C == $M; - C == $P; - C == $V -> - p_avp_flags(Str, [?ATOM([C]) | Acc]); -%% Could support lowercase here if there's a use for distinguishing -%% between Must and Should in the future in deciding whether or not -%% to set a flag. - -p_avp_flags([$-|Str], Acc) -> - %% Require encr on same line as flags if specified. - {H,T} = lists:splitwith(fun(C) -> C /= $\n end, Str), - - {[{name, [$X|X]} | Toks], Rest} = diameter_spec_scan:split([$X|H], 2), - - "" == X orelse throw({?MODULE, {invalid_avp_flag, Str}}), - - Encr = case Toks of - [] -> - "-"; - [{_, E}] -> - (E == "Y" orelse E == "N") - orelse throw({?MODULE, {invalid_encr, E}}), - E - end, - - Flags = ordsets:from_list(lists:reverse(Acc)), - - {Flags, ?ATOM(Encr), Rest ++ T}; - -p_avp_flags(Str, Acc) -> - p_avp_flags([$-|Str], Acc). - -type("DiamIdent") -> "DiameterIdentity"; %% RFC 3588 -type("DiamURI") -> "DiameterURI"; %% RFC 3588 -type("IPFltrRule") -> "IPFilterRule"; %% RFC 4005 -type("QoSFltrRule") -> "QoSFilterRule"; %% RFC 4005 -type(N) - when N == "OctetString"; - N == "Integer32"; - N == "Integer64"; - N == "Unsigned32"; - N == "Unsigned64"; - N == "Float32"; - N == "Float64"; - N == "Grouped"; - N == "Enumerated"; - N == "Address"; - N == "Time"; - N == "UTF8String"; - N == "DiameterIdentity"; - N == "DiameterURI"; - N == "IPFilterRule"; - N == "QoSFilterRule" -> - N; -type(N) -> - ?ERROR({invalid_avp_type, N}). - -%% ------------------------------------------------------------------------ -%% parse_commands/1 - -parse_commands(Str) -> - p_abbr(diameter_spec_scan:parse(Str), []). - - p_abbr([{name, Name}, {name, Abbrev} | Toks], Acc) - when length(Abbrev) < length(Name) -> - p_abbr(Toks, [{?ATOM(Name), ?ATOM(Abbrev)} | Acc]); - -p_abbr([], Acc) -> - lists:reverse(Acc); - -p_abbr(T, _) -> - ?ERROR({invalid_command, T}). diff --git a/lib/diameter/src/compiler/diameter_vsn.hrl b/lib/diameter/src/compiler/diameter_vsn.hrl new file mode 100644 index 0000000000..024d047adc --- /dev/null +++ b/lib/diameter/src/compiler/diameter_vsn.hrl @@ -0,0 +1,22 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% The version of the format of the return value of dict/0 in +%% generated dictionary modules. +-define(VERSION, 1). diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia index f7a0b717cd..acd7fffd00 100644 --- a/lib/diameter/src/dict/base_rfc3588.dia +++ b/lib/diameter/src/dict/base_rfc3588.dia @@ -136,14 +136,14 @@ [ Origin-State-Id ] answer-message ::= < Diameter Header: code, ERR [PXY] > - 0*1 < Session-Id > - { Origin-Host } - { Origin-Realm } - { Result-Code } - [ Origin-State-Id ] - [ Error-Reporting-Host ] - [ Proxy-Info ] - * [ AVP ] + 0*1 < Session-Id > + { Origin-Host } + { Origin-Realm } + { Result-Code } + [ Origin-State-Id ] + [ Error-Reporting-Host ] + [ Proxy-Info ] + * [ AVP ] RAR ::= < Diameter Header: 258, REQ, PXY > < Session-Id > @@ -312,14 +312,14 @@ @enum Termination-Cause - DIAMETER_LOGOUT 1 - DIAMETER_SERVICE_NOT_PROVIDED 2 - DIAMETER_BAD_ANSWER 3 - DIAMETER_ADMINISTRATIVE 4 - DIAMETER_LINK_BROKEN 5 - DIAMETER_AUTH_EXPIRED 6 - DIAMETER_USER_MOVED 7 - DIAMETER_SESSION_TIMEOUT 8 + LOGOUT 1 + SERVICE_NOT_PROVIDED 2 + BAD_ANSWER 3 + ADMINISTRATIVE 4 + LINK_BROKEN 5 + AUTH_EXPIRED 6 + USER_MOVED 7 + SESSION_TIMEOUT 8 @enum Session-Server-Failover @@ -343,14 +343,53 @@ @define Result-Code -;; 7.1.1. Informational + ;; 7.1.1. Informational + MULTI_ROUND_AUTH 1001 + + ;; 7.1.2. Success + SUCCESS 2001 + LIMITED_SUCCESS 2002 + + ;; 7.1.3. Protocol Errors + COMMAND_UNSUPPORTED 3001 + UNABLE_TO_DELIVER 3002 + REALM_NOT_SERVED 3003 + TOO_BUSY 3004 + LOOP_DETECTED 3005 + REDIRECT_INDICATION 3006 + APPLICATION_UNSUPPORTED 3007 + INVALID_HDR_BITS 3008 + INVALID_AVP_BITS 3009 + UNKNOWN_PEER 3010 + + ;; 7.1.4. Transient Failures + AUTHENTICATION_REJECTED 4001 + OUT_OF_SPACE 4002 + ELECTION_LOST 4003 + + ;; 7.1.5. Permanent Failures + AVP_UNSUPPORTED 5001 + UNKNOWN_SESSION_ID 5002 + AUTHORIZATION_REJECTED 5003 + INVALID_AVP_VALUE 5004 + MISSING_AVP 5005 + RESOURCES_EXCEEDED 5006 + CONTRADICTING_AVPS 5007 + AVP_NOT_ALLOWED 5008 + AVP_OCCURS_TOO_MANY_TIMES 5009 + NO_COMMON_APPLICATION 5010 + UNSUPPORTED_VERSION 5011 + UNABLE_TO_COMPLY 5012 + INVALID_BIT_IN_HEADER 5013 + INVALID_AVP_LENGTH 5014 + INVALID_MESSAGE_LENGTH 5015 + INVALID_AVP_BIT_COMBO 5016 + NO_COMMON_SECURITY 5017 + + ;; With a prefix for backwards compatibility. DIAMETER_MULTI_ROUND_AUTH 1001 - -;; 7.1.2. Success DIAMETER_SUCCESS 2001 DIAMETER_LIMITED_SUCCESS 2002 - -;; 7.1.3. Protocol Errors DIAMETER_COMMAND_UNSUPPORTED 3001 DIAMETER_UNABLE_TO_DELIVER 3002 DIAMETER_REALM_NOT_SERVED 3003 @@ -361,13 +400,9 @@ DIAMETER_INVALID_HDR_BITS 3008 DIAMETER_INVALID_AVP_BITS 3009 DIAMETER_UNKNOWN_PEER 3010 - -;; 7.1.4. Transient Failures DIAMETER_AUTHENTICATION_REJECTED 4001 DIAMETER_OUT_OF_SPACE 4002 - ELECTION_LOST 4003 - -;; 7.1.5. Permanent Failures + DIAMETER_ELECTION_LOST 4003 DIAMETER_AVP_UNSUPPORTED 5001 DIAMETER_UNKNOWN_SESSION_ID 5002 DIAMETER_AUTHORIZATION_REJECTED 5003 @@ -412,3 +447,15 @@ ;; E2E-Sequence ::= <AVP Header: 300 > 2* { AVP } + +;; Backwards compatibility. +@define Termination-Cause + + DIAMETER_LOGOUT 1 + DIAMETER_SERVICE_NOT_PROVIDED 2 + DIAMETER_BAD_ANSWER 3 + DIAMETER_ADMINISTRATIVE 4 + DIAMETER_LINK_BROKEN 5 + DIAMETER_AUTH_EXPIRED 6 + DIAMETER_USER_MOVED 7 + DIAMETER_SESSION_TIMEOUT 8 diff --git a/lib/diameter/src/dict/relay.dia b/lib/diameter/src/dict/relay.dia index c22293209b..294014b093 100644 --- a/lib/diameter/src/dict/relay.dia +++ b/lib/diameter/src/dict/relay.dia @@ -21,5 +21,3 @@ @name diameter_gen_relay @prefix diameter_relay @vendor 0 IETF - -@inherits diameter_gen_base_rfc3588 diff --git a/lib/diameter/src/gen/.gitignore b/lib/diameter/src/gen/.gitignore index d490642eb7..3f32313f56 100644 --- a/lib/diameter/src/gen/.gitignore +++ b/lib/diameter/src/gen/.gitignore @@ -1,2 +1,2 @@ - +/diameter_dict_parser.erl /diameter_gen*rl diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index c7cbe598af..7a700a6d53 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -24,10 +24,15 @@ DICTS = \ base_accounting \ relay +# The yecc grammar for the dictionary parser. +DICT_YRL = \ + diameter_dict_parser + # Handwritten (runtime) modules included in the app file. RT_MODULES = \ base/diameter \ base/diameter_app \ + base/diameter_callback \ base/diameter_capx \ base/diameter_config \ base/diameter_codec \ @@ -57,13 +62,13 @@ RT_MODULES = \ # Handwritten (compile time) modules not included in the app file. CT_MODULES = \ - base/diameter_callback \ base/diameter_dbg \ base/diameter_info \ compiler/diameter_codegen \ compiler/diameter_exprecs \ - compiler/diameter_spec_scan \ - compiler/diameter_spec_util \ + compiler/diameter_nowarn \ + compiler/diameter_dict_scanner \ + compiler/diameter_dict_util \ compiler/diameter_make # Released hrl files in ../include intended for public consumption. @@ -74,8 +79,8 @@ EXTERNAL_HRLS = \ # Released hrl files intended for private use. INTERNAL_HRLS = \ base/diameter_internal.hrl \ - base/diameter_types.hrl \ - compiler/diameter_forms.hrl + compiler/diameter_forms.hrl \ + compiler/diameter_vsn.hrl # Released files relative to ../bin. BINS = \ @@ -83,11 +88,17 @@ BINS = \ # Released files relative to ../examples. EXAMPLES = \ - GNUmakefile \ - peer.erl \ - client.erl \ - client_cb.erl \ - server.erl \ - server_cb.erl \ - relay.erl \ - relay_cb.erl + code/GNUmakefile \ + code/peer.erl \ + code/client.erl \ + code/client_cb.erl \ + code/server.erl \ + code/server_cb.erl \ + code/relay.erl \ + code/relay_cb.erl \ + dict/rfc4004_mip.dia \ + dict/rfc4005_nas.dia \ + dict/rfc4006_cc.dia \ + dict/rfc4072_eap.dia \ + dict/rfc4590_digest.dia \ + dict/rfc4740_sip.dia diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 209f8c01c1..68b0342cd5 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -546,10 +546,10 @@ send(Sock, AssocId, Stream, Bin) -> %% recv/2 %% Association established ... -recv({[], #sctp_assoc_change{state = comm_up, - outbound_streams = OS, - inbound_streams = IS, - assoc_id = Id}}, +recv({_, #sctp_assoc_change{state = comm_up, + outbound_streams = OS, + inbound_streams = IS, + assoc_id = Id}}, #transport{assoc_id = undefined, mode = {T, _}, socket = Sock} @@ -562,7 +562,7 @@ recv({[], #sctp_assoc_change{state = comm_up, streams = {IS, OS}}); %% ... or not: try the next address. -recv({[], #sctp_assoc_change{} = E}, +recv({_, #sctp_assoc_change{} = E}, #transport{assoc_id = undefined, socket = Sock, mode = {connect = C, {[RA|RAs], RP, Es}}} @@ -570,7 +570,7 @@ recv({[], #sctp_assoc_change{} = E}, S#transport{mode = {C, connect(Sock, RAs, RP, [{RA,E} | Es])}}; %% Lost association after establishment. -recv({[], #sctp_assoc_change{}}, _) -> +recv({_, #sctp_assoc_change{}}, _) -> stop; %% Inbound Diameter message. @@ -580,7 +580,7 @@ recv({[#sctp_sndrcvinfo{stream = Id}], Bin}, #transport{parent = Pid}) bin = Bin}), ok; -recv({[], #sctp_shutdown_event{assoc_id = Id}}, +recv({_, #sctp_shutdown_event{assoc_id = Id}}, #transport{assoc_id = Id}) -> stop; @@ -593,10 +593,10 @@ recv({[], #sctp_shutdown_event{assoc_id = Id}}, %% disabled by default so don't handle it. We could simply disable %% events we don't react to but don't. -recv({[], #sctp_paddr_change{}}, _) -> +recv({_, #sctp_paddr_change{}}, _) -> ok; -recv({[], #sctp_pdapi_event{}}, _) -> +recv({_, #sctp_pdapi_event{}}, _) -> ok. %% up/1 diff --git a/lib/diameter/test/Makefile b/lib/diameter/test/Makefile index 97d9069f4a..ab5b45ff3d 100644 --- a/lib/diameter/test/Makefile +++ b/lib/diameter/test/Makefile @@ -50,6 +50,8 @@ TARGET_FILES = $(MODULES:%=%.$(EMULATOR)) SUITE_MODULES = $(filter diameter_%_SUITE, $(MODULES)) SUITES = $(SUITE_MODULES:diameter_%_SUITE=%) +DATA_DIRS = $(sort $(dir $(DATA))) + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- @@ -121,12 +123,12 @@ help: # diameter_ct:run/1 itself can't tell (it seems). The absolute -pa is # because ct will change directories. $(SUITES): log opt - $(ERL) -noshell \ + $(ERL) -noinput \ -pa $(realpath ../ebin) \ -sname diameter_test_$@ \ -s diameter_ct run diameter_$@_SUITE \ -s init stop \ - | awk '1{rc=0} {print} / FAILED /{rc=1} END{exit rc}' + | awk '{print} / FAILED /{rc=1} END{exit rc}' rc=0 # Shorter in sed but requires a GNU extension (ie. Q). log: @@ -147,9 +149,7 @@ else include $(ERL_TOP)/make/otp_release_targets.mk endif -release_spec: - -release_docs_spec: +release_spec release_docs_spec: release_tests_spec: $(INSTALL_DIR) $(RELSYSDIR) @@ -157,12 +157,18 @@ release_tests_spec: $(COVER_SPEC_FILE) \ $(HRL_FILES) \ $(RELSYSDIR) + $(MAKE) $(DATA_DIRS:%/=release_data_%) $(MAKE) $(ERL_FILES:%=/%) +$(DATA_DIRS:%/=release_data_%): release_data_%: + $(INSTALL_DIR) $(RELSYSDIR)/$* + $(INSTALL_DATA) $(filter $*/%, $(DATA)) $(RELSYSDIR)/$* + force: .PHONY: release_spec release_docs_spec release_test_specs .PHONY: force +.PHONY: $(DATA_DIRS:%/=release_data_%) # Can't just make $(ERL_FILES:%=/%) phony since then implicit rule # searching is skipped. diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 7f53a4ddd4..53332af626 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_SUITE.erl @@ -41,6 +41,19 @@ -define(A, list_to_atom). +%% Modules not in the app and that should not have dependencies on it +%% for build reasons. +-define(COMPILER_MODULES, [diameter_codegen, + diameter_dict_scanner, + diameter_dict_parser, + diameter_dict_util, + diameter_exprecs, + diameter_nowarn, + diameter_make]). + +-define(HELP_MODULES, [diameter_dbg, + diameter_info]). + %% =========================================================================== suite() -> @@ -93,14 +106,8 @@ vsn(Config) -> modules(Config) -> Mods = fetch(modules, fetch(app, Config)), Installed = code_mods(), - Help = [diameter_callback, - diameter_codegen, - diameter_dbg, - diameter_exprecs, - diameter_info, - diameter_make, - diameter_spec_scan, - diameter_spec_util], + Help = lists:sort(?HELP_MODULES ++ ?COMPILER_MODULES), + {[], Help} = {Mods -- Installed, lists:sort(Installed -- Mods)}. code_mods() -> @@ -167,14 +174,12 @@ xref(Config) -> %% stop xref from complaining about calls to module erlang, which %% was previously in kernel. Erts isn't an application however, in %% the sense that there's no .app file, and isn't listed in - %% applications. Seems less than ideal. Also, diameter_tcp does - %% call ssl despite ssl 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. + %% applications. ok = lists:foreach(fun(A) -> add_application(XRef, A) end, [?APP, erts | fetch(applications, App)]), {ok, Undefs} = xref:analyze(XRef, undefined_function_calls), + {ok, Called} = xref:analyze(XRef, {module_call, ?COMPILER_MODULES}), xref:stop(XRef), @@ -183,7 +188,21 @@ xref(Config) -> lists:member(F, Mods) andalso {F,T} /= {diameter_tcp, ssl} end, - Undefs). + 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). add_application(XRef, App) -> add_application(XRef, App, code:lib_dir(App)). diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl index e6b1558bf6..54a161d606 100644 --- a/lib/diameter/test/diameter_capx_SUITE.erl +++ b/lib/diameter/test/diameter_capx_SUITE.erl @@ -27,8 +27,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_testcase/2, end_per_testcase/2]). @@ -93,30 +91,26 @@ -define(cea, #diameter_base_CEA). -define(answer_message, #'diameter_base_answer-message'). +-define(fail(T), erlang:error({T, process_info(self(), messages)})). + +-define(TIMEOUT, 2000). + %% =========================================================================== suite() -> [{timetrap, {seconds, 10}}]. -all() -> - [start, start_services, add_listeners - | [{group, N} || {N, _, _} <- groups()]] - ++ [remove_listeners, stop_services, stop]. +all() -> [start, + start_services, + add_listeners, + {group, all}, + {group, all, [parallel]}, + remove_listeners, + stop_services, + stop]. groups() -> - Ts = testcases(), - [{grp(P), P, Ts} || P <- [[], [parallel]]]. - -grp([]) -> - sequential; -grp([parallel = P]) -> - P. - -init_per_group(_Name, Config) -> - Config. - -end_per_group(_, _) -> - ok. + [{all, [], lists:flatmap(fun tc/1, tc())}]. %% Generate a unique hostname for each testcase so that watchdogs %% don't prevent a connection from being brought up immediately. @@ -137,9 +131,6 @@ end_per_testcase(Name, Config) -> ok = diameter:remove_transport(?CLIENT, CRef). %% Testcases all come in two flavours, client and server. -testcases() -> - lists:flatmap(fun tc/1, tc()). - tc(Name) -> [?A([C,$_|?L(Name)]) || C <- "cs"]. @@ -270,8 +261,8 @@ s_client_reject(Config) -> ?packet{}}} = Info -> Info - after 2000 -> - fail({LRef, OH}) + after ?TIMEOUT -> + ?fail({LRef, OH}) end. c_client_reject(Config) -> @@ -307,12 +298,12 @@ server_closed(Config, F, RC) -> = Reason, {listen, _}}} -> Reason - after 2000 -> - fail({LRef, OH}) + after ?TIMEOUT -> + ?fail({LRef, OH}) end. %% server_reject/3 - + server_reject(Config, F, RC) -> true = diameter:subscribe(?SERVER), OH = host(Config), @@ -328,8 +319,8 @@ server_reject(Config, F, RC) -> = Reason, {listen, _}}} -> Reason - after 2000 -> - fail({LRef, OH}) + after ?TIMEOUT -> + ?fail({LRef, OH}) end. %% cliient_closed/4 @@ -345,13 +336,13 @@ client_closed(Config, Host, F, RC) -> %% client_recv/1 -client_recv(CRef) -> +client_recv(CRef) -> receive ?event{service = ?CLIENT, info = {closed, CRef, Reason, {connect, _}}} -> Reason - after 2000 -> - fail(CRef) + after ?TIMEOUT -> + ?fail(CRef) end. %% server_capx/3 @@ -373,9 +364,6 @@ client_capx(_, ?caps{origin_host = {[_,$_|"client_reject." ++ _], _}}) -> %% =========================================================================== -fail(T) -> - erlang:error({T, process_info(self(), messages)}). - host(Config) -> {_, H} = lists:keyfind(host, 1, Config), ?HOST(H). diff --git a/lib/diameter/test/diameter_codec_SUITE.erl b/lib/diameter/test/diameter_codec_SUITE.erl index 30c60be8e9..2e219bbb10 100644 --- a/lib/diameter/test/diameter_codec_SUITE.erl +++ b/lib/diameter/test/diameter_codec_SUITE.erl @@ -35,7 +35,8 @@ %% testcases -export([base/1, gen/1, - lib/1]). + lib/1, + unknown/1]). -include("diameter_ct.hrl"). @@ -47,7 +48,7 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [base, gen, lib]. + [base, gen, lib, unknown]. init_per_testcase(gen, Config) -> [{application, ?APP, App}] = diameter_util:consult(?APP, app), @@ -74,3 +75,26 @@ gen([{dicts, Ms} | _]) -> lib(_Config) -> diameter_codec_test:lib(). + +%% Have a separate AVP dictionary just to exercise more code. +unknown(Config) -> + Priv = proplists:get_value(priv_dir, Config), + Data = proplists:get_value(data_dir, Config), + ok = make(Data, "recv.dia"), + ok = make(Data, "avps.dia"), + {ok, _, _} = compile("diameter_test_avps.erl"), + ok = make(Data, "send.dia"), + {ok, _, _} = compile("diameter_test_send.erl"), + {ok, _, _} = compile("diameter_test_recv.erl"), + {ok, _, _} = compile(filename:join([Data, "diameter_test_unknown.erl"]), + [{i, Priv}]), + diameter_test_unknown:run(). + +make(Dir, File) -> + diameter_make:codec(filename:join([Dir, File])). + +compile(File) -> + compile(File, []). + +compile(File, Opts) -> + compile:file(File, [return | Opts]). diff --git a/lib/diameter/test/diameter_codec_SUITE_data/avps.dia b/lib/diameter/test/diameter_codec_SUITE_data/avps.dia new file mode 100644 index 0000000000..c9d80a37a9 --- /dev/null +++ b/lib/diameter/test/diameter_codec_SUITE_data/avps.dia @@ -0,0 +1,25 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +@name diameter_test_avps + +@avp_types + + XXX 111 Unsigned32 M + YYY 222 Unsigned32 - diff --git a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl new file mode 100644 index 0000000000..bce3d78a37 --- /dev/null +++ b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl @@ -0,0 +1,76 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(diameter_test_unknown). + +-compile(export_all). + +%% +%% Test reception of unknown AVP's. +%% + +-include_lib("diameter/include/diameter.hrl"). +-include("diameter_test_send.hrl"). +-include("diameter_test_recv.hrl"). + +-define(HOST, "test.erlang.org"). +-define(REALM, "erlang.org"). + +%% Patterns to match decoded AVP's. +-define(MANDATORY_XXX, #diameter_avp{code = 111}). +-define(NOT_MANDATORY_YYY, #diameter_avp{code = 222}). + +%% Ensure that an unknown AVP with an M flag is regarded as an error +%% while one without an M flag is returned as 'AVP'. + +run() -> + H = #diameter_header{version = 1, + end_to_end_id = 1, + hop_by_hop_id = 1}, + Vs = [{'Origin-Host', ?HOST}, + {'Origin-Realm', ?REALM}, + {'XXX', [0]}, + {'YYY', [1]}], + Pkt = #diameter_packet{header = H, + msg = Vs}, + + [] = diameter_util:run([{?MODULE, [run, M, enc(M, Pkt)]} + || M <- ['AR','BR']]). + +enc(M, #diameter_packet{msg = Vs} = P) -> + diameter_codec:encode(diameter_test_send, + P#diameter_packet{msg = [M|Vs]}). + +run(M, Pkt) -> + dec(M, diameter_codec:decode(diameter_test_recv, Pkt)). +%% Note that the recv dictionary defines neither XXX nor YYY. + +dec('AR', #diameter_packet + {msg = #recv_AR{'Origin-Host' = ?HOST, + 'Origin-Realm' = ?REALM, + 'AVP' = [?NOT_MANDATORY_YYY]}, + errors = [{5001, ?MANDATORY_XXX}]}) -> + ok; + +dec('BR', #diameter_packet + {msg = #recv_BR{'Origin-Host' = ?HOST, + 'Origin-Realm' = ?REALM}, + errors = [{5008, ?NOT_MANDATORY_YYY}, + {5001, ?MANDATORY_XXX}]}) -> + ok. diff --git a/lib/diameter/test/diameter_codec_SUITE_data/recv.dia b/lib/diameter/test/diameter_codec_SUITE_data/recv.dia new file mode 100644 index 0000000000..15fec5a5dd --- /dev/null +++ b/lib/diameter/test/diameter_codec_SUITE_data/recv.dia @@ -0,0 +1,51 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +@id 17 +@name diameter_test_recv +@prefix recv + +@inherits diameter_gen_base_rfc3588 + + Origin-Host + Origin-Realm + Result-Code + +@messages + + AR ::= < Diameter Header: 123, REQ > + { Origin-Host } + { Origin-Realm } + * [ AVP ] + + AA ::= < Diameter Header: 123 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + * [ AVP ] + + BR ::= < Diameter Header: 124, REQ > + { Origin-Host } + { Origin-Realm } + + BA ::= < Diameter Header: 124 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + * [ AVP ] diff --git a/lib/diameter/test/diameter_codec_SUITE_data/send.dia b/lib/diameter/test/diameter_codec_SUITE_data/send.dia new file mode 100644 index 0000000000..1472f146ae --- /dev/null +++ b/lib/diameter/test/diameter_codec_SUITE_data/send.dia @@ -0,0 +1,56 @@ +;; +;; %CopyrightBegin% +;; +;; Copyright Ericsson AB 2010-2011. 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% +;; + +@id 17 +@name diameter_test_send +@prefix send + +@inherits diameter_gen_base_rfc3588 + + Origin-Host + Origin-Realm + Result-Code + +@inherits diameter_test_avps + +@messages + + AR ::= < Diameter Header: 123, REQ > + { Origin-Host } + { Origin-Realm } + [ XXX ] + [ YYY ] + + AA ::= < Diameter Header: 123 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + * [ AVP ] + + BR ::= < Diameter Header: 124, REQ > + { Origin-Host } + { Origin-Realm } + [ XXX ] + [ YYY ] + + BA ::= < Diameter Header: 124 > + { Result-Code } + { Origin-Host } + { Origin-Realm } + * [ AVP ] diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index 8046ca4c04..fbd38067a8 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -30,6 +30,9 @@ -define(BASE, diameter_gen_base_rfc3588). -define(BOOL, [true, false]). +-define(A, list_to_atom). +-define(S, atom_to_list). + %% =========================================================================== %% Interface. @@ -42,7 +45,7 @@ gen(Mod) -> command_codes, avp_types, grouped, - enums, + enum, import_avps, import_groups, import_enums]]). @@ -133,7 +136,7 @@ types() -> gen(M, T) -> [] = run(lists:map(fun(X) -> {?MODULE, [gen, M, T, X]} end, - fetch(T, M:dict()))). + fetch(T, dict(M)))). fetch(T, Spec) -> case orddict:find(T, Spec) of @@ -143,6 +146,10 @@ fetch(T, Spec) -> [] end. +gen(M, messages = T, {Name, Code, Flags, ApplId, Avps}) + when is_list(Name) -> + gen(M, T, {?A(Name), Code, Flags, ApplId, Avps}); + gen(M, messages, {Name, Code, Flags, _, _}) -> Rname = M:msg2rec(Name), Name = M:rec2msg(Rname), @@ -156,22 +163,16 @@ gen(M, messages, {Name, Code, Flags, _, _}) -> end, [] = arity(M, Name, Rname); -gen(M, command_codes = T, {Code, {Req, Abbr}, Ans}) -> - Rname = M:msg2rec(Req), - Rname = M:msg2rec(Abbr), - gen(M, T, {Code, Req, Ans}); - -gen(M, command_codes = T, {Code, Req, {Ans, Abbr}}) -> - Rname = M:msg2rec(Ans), - Rname = M:msg2rec(Abbr), - gen(M, T, {Code, Req, Ans}); - gen(M, command_codes, {Code, Req, Ans}) -> - Msgs = orddict:fetch(messages, M:dict()), + Msgs = orddict:fetch(messages, dict(M)), {_, Code, _, _, _} = lists:keyfind(Req, 1, Msgs), {_, Code, _, _, _} = lists:keyfind(Ans, 1, Msgs); -gen(M, avp_types, {Name, Code, Type, _Flags, _Encr}) -> +gen(M, avp_types = T, {Name, Code, Type, Flags}) + when is_list(Name) -> + gen(M, T, {?A(Name), Code, ?A(Type), Flags}); + +gen(M, avp_types, {Name, Code, Type, _Flags}) -> {Code, Flags, VendorId} = M:avp_header(Name), 0 = Flags band 2#00011111, V = undefined /= VendorId, @@ -181,11 +182,19 @@ gen(M, avp_types, {Name, Code, Type, _Flags, _Encr}) -> B = z(B), [] = avp_decode(M, Type, Name); +gen(M, grouped = T, {Name, Code, Vid, Avps}) + when is_list(Name) -> + gen(M, T, {?A(Name), Code, Vid, Avps}); + gen(M, grouped, {Name, _, _, _}) -> Rname = M:name2rec(Name), [] = arity(M, Name, Rname); -gen(M, enums, {Name, ED}) -> +gen(M, enum = T, {Name, ED}) + when is_list(Name) -> + gen(M, T, {?A(Name), lists:map(fun({E,D}) -> {?A(E), D} end, ED)}); + +gen(M, enum, {Name, ED}) -> [] = run([{?MODULE, [enum, M, Name, T]} || T <- ED]); gen(M, Tag, {_Mod, L}) -> @@ -253,17 +262,17 @@ arity(M, Name, AvpName, Rec) -> %% enum/3 -enum(M, Name, {E,_}) -> +enum(M, Name, {_,E}) -> B = <<E:32/integer>>, B = M:avp(encode, E, Name), E = M:avp(decode, B, Name). retag(import_avps) -> avp_types; retag(import_groups) -> grouped; -retag(import_enums) -> enums; +retag(import_enums) -> enum; retag(avp_types) -> import_avps; -retag(enums) -> import_enums. +retag(enum) -> import_enums. %% =========================================================================== @@ -370,8 +379,8 @@ values('Time') -> %% wrapped as for values/1. values('Enumerated', Name, Mod) -> - {_Name, Vals} = lists:keyfind(Name, 1, types(enums, Mod)), - lists:map(fun({N,_}) -> N end, Vals); + {_Name, Vals} = lists:keyfind(?S(Name), 1, types(enum, Mod)), + lists:map(fun({_,N}) -> N end, Vals); values('Grouped', Name, Mod) -> Rname = Mod:name2rec(Name), @@ -400,8 +409,8 @@ values('AVP', _) -> values(Name, Mod) -> Avps = types(avp_types, Mod), - {Name, _Code, Type, _Flags, _Encr} = lists:keyfind(Name, 1, Avps), - b(values(Type, Name, Mod)). + {_Name, _Code, Type, _Flags} = lists:keyfind(?S(Name), 1, Avps), + b(values(?A(Type), Name, Mod)). %% group/5 %% @@ -467,7 +476,7 @@ types(T, Mod) -> types(T, retag(T), Mod). types(T, IT, Mod) -> - Dict = Mod:dict(), + Dict = dict(Mod), fetch(T, Dict) ++ lists:flatmap(fun({_,As}) -> As end, fetch(IT, Dict)). %% random/[12] @@ -498,3 +507,8 @@ flatten({_, {{badmatch, [{_, {{badmatch, _}, _}} | _] = L}, _}}) -> L; flatten(T) -> [T]. + +%% dict/1 + +dict(Mod) -> + tl(Mod:dict()). diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl new file mode 100644 index 0000000000..3b4c9706e0 --- /dev/null +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -0,0 +1,489 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% Tests of the dictionary file compiler. +%% + +-module(diameter_compiler_SUITE). + +-export([suite/0, + all/0, + init_per_suite/1, + end_per_suite/1]). + +%% testcases +-export([format/1, format/2, + replace/1, replace/2, + generate/1, generate/4, generate/0, + examples/1, examples/0]). + +-export([dict/0]). %% fake dictionary module + +-define(base, "base_rfc3588.dia"). +-define(util, diameter_util). +-define(S, atom_to_list). +-define(L, integer_to_list). + +%% =========================================================================== + +%% RE/Replacement (in the sense of re:replace/4) pairs for morphing +%% base_rfc3588.dia. The key is 'ok' or the the expected error as +%% returned in the first element of the error tuple returned by +%% diameter_dict_util:parse/2. +-define(REPLACE, + [{ok, + "", + ""}, + {scan, + "@id 0", + "@id \\&"}, + {scan, + "@name ", + "&'"}, + {parse, + "@id 0", + "@id @id"}, + {avp_code_already_defined, + "480", + "485"}, + {uint32_out_of_range, + "@id 0", + "@id 4294967296"}, + {uint32_out_of_range, + "@vendor 0", + "@vendor 4294967296"}, + {uint32_out_of_range, + [{"^ *Failed-AVP .*$", "&V"}, + {"@avp_types", "@avp_vendor_id 4294967296 Failed-AVP\n&"}]}, + {imported_avp_already_defined, + "@avp_types", + "@inherits diameter_gen_base_rfc3588 &"}, + {duplicate_import, + [{"@avp_types", "@inherits diameter_gen_base_rfc3588 Class\n&"}, + {"@avp_types", "@inherits diameter_gen_base_rfc3588\n&"}, + {"^@avp_types[^@]*", ""}, + {"^@enum[^&]*", ""}]}, + {duplicate_section, + "@prefix", + "@name"}, + {already_declared, + "@enum Termination-Cause", + "& XXX 0\n &"}, + {already_declared, + "@define Result-Code", + "& XXX 1000 &"}, + {inherited_avp_already_defined, + "@id", + "@inherits nomod Origin-Host &"}, + {avp_already_defined, + "@avp_types", + "@inherits m XXX\nXXX\n&"}, + {avp_already_defined, + "@avp_types", + "@inherits mod1 XXX\n@inherits mod2 XXX\n&"}, + {key_already_defined, + "DIAMETER_SUCCESS", + "& 2001\n&"}, + {messages_without_id, + "@id 0", + ""}, + {avp_name_already_defined, + "Class", + "& 666 Time M\n&"}, + {avp_has_unknown_type, + "Enumerated", + "Enum"}, + {avp_has_invalid_flag, + " -", + " X"}, + {avp_has_duplicate_flag, + " -", + " MM"}, + {avp_has_vendor_id, + "@avp_types", + "@avp_vendor_id 667 Class\n&"}, + {avp_has_no_vendor, + [{"^ *Class .*$", "&V"}, + {"@vendor .*", ""}]}, + {group_already_defined, + "@grouped", + "& Failed-AVP ::= < AVP Header: 279 > " "{AVP}\n&"}, + {grouped_avp_code_mismatch, + "(Failed-AVP ::= [^0-9]*27)9", + "&8"}, + {grouped_avp_has_wrong_type, + "(Failed-AVP *279 *)Grouped", + "\\1Time"}, + {grouped_avp_not_defined, + "Failed-AVP *.*", + ""}, + {grouped_vendor_id_without_flag, + "(Failed-AVP .*)>", + "\\1 668>"}, + {grouped_vendor_id_mismatch, + [{"(Failed-AVP .*)>", "\\1 17>"}, + {"^ *Failed-AVP .*$", "&V"}, + {"@avp_types", "@avp_vendor_id 18 Failed-AVP\n&"}]}, + {ok, + [{"(Failed-AVP .*)>", "\\1 17>"}, + {"^ *Failed-AVP .*$", "&V"}]}, + {message_name_already_defined, + "CEA ::= .*:", + "& 257 > {Result-Code}\n&"}, + {message_code_already_defined, + "CEA( ::= .*)", + "XXX\\1 {Result-Code}\n&"}, + {message_has_duplicate_flag, + "(CER ::=.*)>", + "\\1, REQ>"}, + {message_application_id_mismatch, + "(CER ::=.*)>", + "\\1 1>"}, + {invalid_avp_order, + "CEA ::=", + "{Result-Code} &"}, + {ok, + "{ Product-Name", + "* &"}, + {required_avp_has_zero_max_arity, + "{ Product-Name", + "*0 &"}, + {required_avp_has_zero_min_arity, + "{ Product-Name", + "0* &"}, + {required_avp_has_zero_min_arity, + "{ Product-Name", + "0*0 &"}, + {ok, + "{ Product-Name", + "*1 &"}, + {ok, + "{ Product-Name", + "1* &"}, + {ok, + "{ Product-Name", + "1*1 &"}, + {ok, + "{ Product-Name", + "2* &"}, + {ok, + "{ Product-Name", + "*2 &"}, + {ok, + "{ Product-Name", + "2*2 &"}, + {ok, + "{ Product-Name", + "2*3 &"}, + {qualifier_has_min_greater_than_max, + "{ Product-Name", + "3*2 &"}, + {ok, + "\\[ Origin-State-Id", + "* &"}, + {ok, + "\\[ Origin-State-Id", + "0* &"}, + {ok, + "\\[ Origin-State-Id", + "*0 &"}, + {ok, + "\\[ Origin-State-Id", + "0*0 &"}, + {ok, + "\\[ Origin-State-Id", + "0*1 &"}, + {ok, + "\\[ Origin-State-Id", + "0*2 &"}, + {ok, + "\\[ Origin-State-Id", + "*1 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "1* &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "1*1 &"}, + {ok, + "\\[ Origin-State-Id", + "*2 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2* &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2*2 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2*3 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "3*2 &"}, + {ok, + "^ *< Session-Id", + "* &"}, + {ok, + "^ *< Session-Id", + "*0 &"}, + {ok, + "^ *< Session-Id", + "0* &"}, + {ok, + "^ *< Session-Id", + "0*0 &"}, + {ok, + "^ *< Session-Id", + "0*1 &"}, + {ok, + "^ *< Session-Id", + "0*2 &"}, + {ok, + "^ *< Session-Id", + "*1 &"}, + {ok, + "^ *< Session-Id", + "1* &"}, + {ok, + "^ *< Session-Id", + "1*1 &"}, + {ok, + "^ *< Session-Id", + "*2 &"}, + {ok, + "^ *< Session-Id", + "2* &"}, + {ok, + "^ *< Session-Id", + "2*2 &"}, + {ok, + "^ *< Session-Id", + "2*3 &"}, + {qualifier_has_min_greater_than_max, + "^ *< Session-Id", + "3*2 &"}, + {avp_already_referenced, + "CER ::=.*", + "& {Origin-Host}"}, + {message_missing, + "CER ::=", + "XXR ::= < Diameter-Header: 666, REQ > {Origin-Host} &"}, + {requested_avp_not_found, + [{"@id", "@inherits diameter_gen_base_rfc3588 XXX &"}, + {"CEA ::=", "<XXX> &"}]}, + {requested_avp_not_found, + [{"@id", "@inherits diameter_gen_base_rfc3588 'X X X' &"}, + {"CEA ::=", "<'X X X'> &"}]}, + {enumerated_avp_has_wrong_local_type, + "Enumerated", + "Time"}, + {enumerated_avp_not_defined, + [{"{ Disconnect-Cause }", ""}, + {"^ *Disconnect-Cause .*", ""}]}, + {avp_not_defined, + "CEA ::=", + "<XXX> &"}, + {not_loaded, + [{"@avp_types", "@inherits nomod XXX &"}, + {"CEA ::=", "<XXX> &"}]}, + {recompile, + [{"@avp_types", "@inherits " ++ ?S(?MODULE) ++ " XXX &"}, + {"CEA ::=", "<XXX> &"}]}, + {no_dict, + [{"@avp_types", "@inherits diameter XXX &"}, + {"CEA ::=", "<XXX> &"}]}, + {ok, + "@avp_types", + "@end & bad syntax"}, + {parse, + "@avp_types", + "& bad syntax"}, + {ok, + [{"@avp_types", "& 3XXX 666 Time M 'X X X' 667 Time -"}, + {"^ *Class .*", "@avp_types"}, + {"^ *Failed-AVP ", "@avp_types &"}, + {"@grouped", "&&"}, + {"^ *Failed-AVP ::=", "@grouped &"}, + {"CEA ::=", "<'Class'> &"}, + {"@avp_types", "@inherits diameter_gen_base_rfc3588 Class\n&"}, + {"@avp_types", "@custom_types mymod " + "Product-Name Firmware-Revision\n" + "@codecs mymod " + "Origin-Host Origin-Realm\n&"}]}]). + +%% Standard dictionaries in examples/dict. +-define(EXAMPLES, [rfc4004_mip, + rfc4005_nas, + rfc4006_cc, + rfc4072_eap, + rfc4590_digest, + rfc4740_sip]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 5}}]. + +all() -> + [format, + replace, + generate, + examples]. + +%% Error handling testcases will make an erroneous dictionary out of +%% the base dictionary and check that the expected error results. +%% ?REPLACE encodes the modifications and expected error. +init_per_suite(Config) -> + Path = filename:join([code:lib_dir(diameter, src), "dict", ?base]), + {ok, Bin} = file:read_file(Path), + [{base, Bin} | Config]. + +end_per_suite(_Config) -> + ok. + +%% =========================================================================== +%% format/1 +%% +%% Ensure that parse o format is the identity map. + +format(Config) -> + Bin = proplists:get_value(base, Config), + [] = ?util:run([{?MODULE, [format, M, Bin]} + || E <- ?REPLACE, + {ok, M} <- [norm(E)]]). + +format(Mods, Bin) -> + B = modify(Bin, Mods), + {ok, Dict} = diameter_dict_util:parse(B, []), + {ok, D} = diameter_dict_util:parse(diameter_dict_util:format(Dict), []), + {Dict, Dict} = {Dict, D}. + +%% =========================================================================== +%% replace/1 +%% +%% Ensure the expected success/error when parsing a morphed common +%% dictionary. + +replace(Config) -> + Bin = proplists:get_value(base, Config), + [] = ?util:run([{?MODULE, [replace, N, Bin]} + || E <- ?REPLACE, + N <- [norm(E)]]). + +replace({E, Mods}, Bin) -> + B = modify(Bin, Mods), + case {E, diameter_dict_util:parse(B, [{include, here()}]), Mods} of + {ok, {ok, Dict}, _} -> + Dict; + {_, {error, {E,_} = T}, _} -> + S = diameter_dict_util:format_error(T), + true = nochar($", S, E), + true = nochar($', S, E), + S + end. + +re({RE, Repl}, Bin) -> + re:replace(Bin, RE, Repl, [multiline]). + +%% =========================================================================== +%% generate/1 +%% +%% Ensure success when generating code and compiling. + +generate() -> + [{timetrap, {seconds, 2*length(?REPLACE)}}]. + +generate(Config) -> + Bin = proplists:get_value(base, Config), + Rs = lists:zip(?REPLACE, lists:seq(1, length(?REPLACE))), + [] = ?util:run([{?MODULE, [generate, M, Bin, N, T]} + || {E,N} <- Rs, + {ok, M} <- [norm(E)], + T <- [erl, hrl, spec]]). + +generate(Mods, Bin, N, Mode) -> + B = modify(Bin, Mods ++ [{"@name .*", "@name dict" ++ ?L(N)}]), + {ok, Dict} = diameter_dict_util:parse(B, []), + File = "dict" ++ integer_to_list(N), + {_, ok} = {Dict, diameter_codegen:from_dict("dict", + Dict, + [{name, File}, + {prefix, "base"}, + debug], + Mode)}, + Mode == erl + andalso ({ok, _} = compile:file(File ++ ".erl", [return_errors])). + +%% =========================================================================== +%% examples/1 +%% +%% Compile dictionaries extracted from various standards. + +examples() -> + [{timetrap, {seconds, 3*length(?EXAMPLES)}}]. + +examples(_Config) -> + Dir = filename:join([code:lib_dir(diameter, examples), "dict"]), + [D || D <- ?EXAMPLES, _ <- [examples(?S(D), Dir)]]. + +examples(Dict, Dir) -> + {Name, Pre} = make_name(Dict), + ok = diameter_make:codec(filename:join([Dir, Dict ++ ".dia"]), + [{name, Name}, + {prefix, Pre}, + inherits("rfc3588_base") + | opts(Dict)]), + {ok, _, _} = compile:file(Name ++ ".erl", [return]). + +opts(M) + when M == "rfc4006_cc"; + M == "rfc4072_eap" -> + [inherits("rfc4005_nas")]; +opts("rfc4740_sip") -> + [inherits("rfc4590_digest")]; +opts(_) -> + []. + +inherits(File) -> + {Name, _} = make_name(File), + {inherits, File ++ "/" ++ Name}. + +make_name(File) -> + {R, [$_|N]} = lists:splitwith(fun(C) -> C /= $_ end, File), + {string:join(["diameter_gen", N, R], "_"), "diameter_" ++ N}. + +%% =========================================================================== + +modify(Bin, Mods) -> + lists:foldl(fun re/2, Bin, Mods). + +norm({E, RE, Repl}) -> + {E, [{RE, Repl}]}; +norm({_,_} = T) -> + T. + +nochar(Char, Str, Err) -> + Err == parse orelse not lists:member(Char, Str) orelse Str. + +here() -> + filename:dirname(code:which(?MODULE)). + +dict() -> + [0 | orddict:new()]. diff --git a/lib/diameter/test/diameter_dict_SUITE.erl b/lib/diameter/test/diameter_dict_SUITE.erl index 87bb9727fe..5cf8506d3f 100644 --- a/lib/diameter/test/diameter_dict_SUITE.erl +++ b/lib/diameter/test/diameter_dict_SUITE.erl @@ -25,9 +25,7 @@ -export([suite/0, all/0, - groups/0, - init_per_group/2, - end_per_group/2]). + groups/0]). %% testcases -export([append/1, @@ -53,10 +51,11 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [{group, all} | tc()]. + [{group, all}, + {group, all, [parallel]}]. groups() -> - [{all, [parallel], tc()}]. + [{all, [], tc()}]. tc() -> [append, @@ -71,12 +70,6 @@ tc() -> update, update_counter]. -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. - %% =========================================================================== -define(KV100, [{N,[N]} || N <- lists:seq(1,100)]). diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index f4d62f94c6..53398dd93e 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_SUITE.erl @@ -48,18 +48,13 @@ stop/1]). %% diameter callbacks --export([peer_up/3, - peer_down/3, - pick_peer/4, +-export([pick_peer/4, prepare_request/3, - prepare_retransmit/3, handle_answer/4, - handle_error/4, handle_request/3]). -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== @@ -91,7 +86,12 @@ {'Acct-Application-Id', [Dict:id()]}, {application, [{alias, ?APP_ALIAS}, {dictionary, Dict}, - {module, ?MODULE}, + {module, #diameter_callback + {peer_up = false, + peer_down = false, + handle_error = false, + prepare_retransmit = false, + default = ?MODULE}}, {answer_errors, callback}]}]). -define(SUCCESS, 2001). @@ -174,23 +174,13 @@ realm(Host) -> call(Req, Opts) -> diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). - + set([H|T], Vs) -> [H | Vs ++ T]. %% =========================================================================== %% diameter callbacks -%% peer_up/3 - -peer_up(_SvcName, _Peer, State) -> - State. - -%% peer_down/3 - -peer_down(_SvcName, _Peer, State) -> - State. - %% pick_peer/4 %% Choose a server other than SERVER3 or SERVER5 if possible. @@ -219,22 +209,12 @@ prepare(#diameter_packet{msg = Req}, Caps) -> {'Origin-Host', OH}, {'Origin-Realm', OR}]). -%% prepare_retransmit/3 - -prepare_retransmit(Pkt, ?CLIENT, _Peer) -> - {send, Pkt}. - %% handle_answer/4 handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. -%% handle_error/4 - -handle_error(Reason, _Req, ?CLIENT, _Peer) -> - {error, Reason}. - %% handle_request/3 %% Only SERVER3 actually answers. diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl new file mode 100644 index 0000000000..7f435a6b7a --- /dev/null +++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl @@ -0,0 +1,354 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% Some gen_sctp-specific tests demonstrating problems that were +%% encountered during diameter development but have nothing +%% specifically to do with diameter. At least one of them can cause +%% diameter_transport_SUITE testcases to fail. +%% + +-module(diameter_gen_sctp_SUITE). + +-export([suite/0, + all/0, + init_per_suite/1, + end_per_suite/1]). + +%% testcases +-export([send_not_from_controlling_process/1, + send_from_multiple_clients/1, + receive_what_was_sent/1]). + +-include_lib("kernel/include/inet_sctp.hrl"). + +%% Message from gen_sctp are of this form. +-define(SCTP(Sock, Data), {sctp, Sock, _, _, Data}). + +%% Open sockets on the loopback address. +-define(ADDR, {127,0,0,1}). + +%% Snooze, nap, siesta. +-define(SLEEP(T), receive after T -> ok end). + +%% An indescribably long number of milliseconds after which everthing +%% that should have happened has. +-define(FOREVER, 2000). + +%% The first byte in each message we send as a simple guard against +%% not receiving what was sent. +-define(MAGIC, 42). + +%% =========================================================================== + +suite() -> + [{timetrap, {minutes, 2}}]. + +all() -> + [send_not_from_controlling_process, + send_from_multiple_clients, + receive_what_was_sent]. + +init_per_suite(Config) -> + case gen_sctp:open() of + {ok, Sock} -> + gen_sctp:close(Sock), + Config; + {error, E} when E == eprotonosupport; + E == esocktnosupport -> + {skip, no_sctp} + end. + +end_per_suite(_Config) -> + ok. + +%% =========================================================================== + +%% send_not_from_controlling_process/1 +%% +%% This testcase failing shows gen_sctp:send/4 hanging when called +%% outside the controlling process of the socket in question. + +send_not_from_controlling_process(_) -> + Pids = send_not_from_controlling_process(), + ?SLEEP(?FOREVER), + try + [] = [{P,I} || P <- Pids, I <- [process_info(P)], I /= undefined] + after + lists:foreach(fun(P) -> exit(P, kill) end, Pids) + end. + +%% send_not_from_controlling_process/0 +%% +%% Returns the pids of three spawned processes: a listening process, a +%% connecting process and a sending process. +%% +%% The expected behaviour is that all three processes exit: +%% +%% - The listening process exits upon receiving an SCTP message +%% sent by the sending process. +%% - The connecting process exits upon listening process exit. +%% - The sending process exits upon gen_sctp:send/4 return. +%% +%% The observed behaviour is that all three processes remain alive +%% indefinitely: +%% +%% - The listening process never receives the SCTP message sent +%% by the sending process. +%% - The connecting process has an inet_reply message in its mailbox +%% as a consequence of the call to gen_sctp:send/4 call from the +%% sending process. +%% - The call to gen_sctp:send/4 in the sending process doesn't return, +%% hanging in prim_inet:getopts/2. + +send_not_from_controlling_process() -> + FPid = self(), + {L, MRef} = spawn_monitor(fun() -> listen(FPid) end),%% listening process + receive + {?MODULE, C, S} -> + erlang:demonitor(MRef, [flush]), + [L,C,S]; + {'DOWN', MRef, process, _, _} = T -> + error(T) + end. + +%% listen/1 + +listen(FPid) -> + {ok, Sock} = open(), + ok = gen_sctp:listen(Sock, true), + {ok, PortNr} = inet:port(Sock), + LPid = self(), + spawn(fun() -> connect1(PortNr, FPid, LPid) end), %% connecting process + Id = assoc(Sock), + ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], _Bin}) + = recv(). %% Waits with this as current_function. + +%% recv/0 + +recv() -> + receive T -> T end. + +%% connect1/3 + +connect1(PortNr, FPid, LPid) -> + {ok, Sock} = open(), + ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []), + Id = assoc(Sock), + FPid ! {?MODULE, + self(), + spawn(fun() -> send(Sock, Id) end)}, %% sending process + MRef = erlang:monitor(process, LPid), + down(MRef). %% Waits with this as current_function. + +%% down/1 + +down(MRef) -> + receive {'DOWN', MRef, process, _, Reason} -> Reason end. + +%% send/2 + +send(Sock, Id) -> + ok = gen_sctp:send(Sock, Id, 0, <<0:32>>). + +%% =========================================================================== + +%% send_from_multiple_clients/0 +%% +%% Demonstrates sluggish delivery of messages. + +send_from_multiple_clients(_) -> + {S, Rs} = T = send_from_multiple_clients(8, 1024), + {false, [], _} = {?FOREVER < S, + Rs -- [OI || {O,_} = OI <- Rs, is_integer(O)], + T}. + +%% send_from_multiple_clients/2 +%% +%% Opens a listening socket and then spawns a specified number of +%% processes, each of which connects to the listening socket. Each +%% connecting process then sends a message, whose size in bytes is +%% passed as an argument, the listening process sends a reply +%% containing the time at which the message was received, and the +%% connecting process then exits upon reception of this reply. +%% +%% Returns the elapsed time for all connecting process to exit +%% together with a list of exit reasons for the connecting processes. +%% In the successful case a connecting process exits with the +%% outbound/inbound transit times for the sent/received message as +%% reason. +%% +%% The observed behaviour is that some outbound messages (that is, +%% from a connecting process to the listening process) can take an +%% unexpectedly long time to complete their journey. The more +%% connecting processes, the longer the possible delay it seems. +%% +%% eg. (With F = fun send_from_multiple_clients/2.) +%% +%% 5> F(2, 1024). +%% {875,[{128,116},{113,139}]} +%% 6> F(4, 1024). +%% {2995290,[{2994022,250},{2994071,80},{200,130},{211,113}]} +%% 7> F(8, 1024). +%% {8997461,[{8996161,116}, +%% {2996471,86}, +%% {2996278,116}, +%% {2996360,95}, +%% {246,112}, +%% {213,159}, +%% {373,173}, +%% {376,118}]} +%% 8> F(8, 1024). +%% {21001891,[{20999968,128}, +%% {8997891,172}, +%% {8997927,91}, +%% {2995716,164}, +%% {2995860,87}, +%% {134,100}, +%% {117,98}, +%% {149,125}]} + +send_from_multiple_clients(N, Sz) + when is_integer(N), 0 < N, is_integer(Sz), 0 < Sz -> + timer:tc(fun listen/2, [N, <<?MAGIC, 0:Sz/unit:8>>]). + +%% listen/2 + +listen(N, Bin) -> + {ok, Sock} = open(), + ok = gen_sctp:listen(Sock, true), + {ok, PortNr} = inet:port(Sock), + + %% Spawn a middleman that in turn spawns N connecting processes, + %% collects a list of exit reasons and then exits with the list as + %% reason. loop/3 returns when we receive this list from the + %% middleman's 'DOWN'. + + Self = self(), + Fun = fun() -> exit(connect2(Self, PortNr, Bin)) end, + {_, MRef} = spawn_monitor(fun() -> exit(fold(N, Fun)) end), + loop(Sock, MRef, Bin). + +%% fold/2 +%% +%% Spawn N processes and collect their exit reasons in a list. + +fold(N, Fun) -> + start(N, Fun), + acc(N, []). + +start(0, _) -> + ok; +start(N, Fun) -> + spawn_monitor(Fun), + start(N-1, Fun). + +acc(0, Acc) -> + Acc; +acc(N, Acc) -> + receive + {'DOWN', _MRef, process, _, RC} -> + acc(N-1, [RC | Acc]) + end. + +%% loop/3 + +loop(Sock, MRef, Bin) -> + receive + ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B}) -> + Sz = size(Bin), + {Sz, Bin} = {size(B), B}, %% assert + ok = send(Sock, Id, mark(Bin)), + loop(Sock, MRef, Bin); + ?SCTP(Sock, _) -> + loop(Sock, MRef, Bin); + {'DOWN', MRef, process, _, Reason} -> + Reason + end. + +%% connect2/3 + +connect2(Pid, PortNr, Bin) -> + erlang:monitor(process, Pid), + + {ok, Sock} = open(), + ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []), + Id = assoc(Sock), + + %% T1 = time before send + %% T2 = time after listening process received our message + %% T3 = time after reply is received + + T1 = now(), + ok = send(Sock, Id, Bin), + T2 = unmark(recv(Sock, Id)), + T3 = now(), + {timer:now_diff(T2, T1), timer:now_diff(T3, T2)}. %% {Outbound, Inbound} + +%% recv/2 + +recv(Sock, Id) -> + receive + ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin}) -> + Bin; + T -> %% eg. 'DOWN' + exit(T) + end. + +%% send/3 + +send(Sock, Id, Bin) -> + gen_sctp:send(Sock, Id, 0, Bin). + +%% mark/1 + +mark(Bin) -> + Info = term_to_binary(now()), + <<Info/binary, Bin/binary>>. + +%% unmark/1 + +unmark(Bin) -> + {_,_,_} = binary_to_term(Bin). + +%% =========================================================================== + +%% receive_what_was_sent/1 +%% +%% Demonstrates reception of a message that differs from that sent. + +receive_what_was_sent(_Config) -> + send_from_multiple_clients(1, 1024*32). %% fails + +%% =========================================================================== + +%% open/0 + +open() -> + gen_sctp:open([{ip, ?ADDR}, {port, 0}, {active, true}, binary]). + +%% assoc/1 + +assoc(Sock) -> + receive + ?SCTP(Sock, {[], #sctp_assoc_change{state = S, + assoc_id = Id}}) -> + comm_up = S, %% assert + Id + end. diff --git a/lib/diameter/test/diameter_reg_SUITE.erl b/lib/diameter/test/diameter_reg_SUITE.erl index ade824c9dd..ec6a0ca731 100644 --- a/lib/diameter/test/diameter_reg_SUITE.erl +++ b/lib/diameter/test/diameter_reg_SUITE.erl @@ -26,8 +26,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_suite/1, end_per_suite/1]). @@ -48,10 +46,11 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [{group, all} | tc()]. + [{group, all}, + {group, all, [parallel]}]. groups() -> - [{all, [parallel], tc()}]. + [{all, [], tc()}]. tc() -> [add, @@ -61,12 +60,6 @@ tc() -> terms, pids]. -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. - init_per_suite(Config) -> ok = diameter:start(), Config. diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl index 40cbdf805a..70e1866791 100644 --- a/lib/diameter/test/diameter_relay_SUITE.erl +++ b/lib/diameter/test/diameter_relay_SUITE.erl @@ -35,9 +35,7 @@ -export([suite/0, all/0, - groups/0, - init_per_group/2, - end_per_group/2]). + groups/0]). %% testcases -export([start/1, @@ -55,18 +53,14 @@ stop/1]). %% diameter callbacks --export([peer_up/3, - peer_down/3, - pick_peer/4, +-export([pick_peer/4, prepare_request/3, prepare_retransmit/3, handle_answer/4, - handle_error/4, handle_request/3]). -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== @@ -102,7 +96,10 @@ {'Acct-Application-Id', [Dict:id()]}, {application, [{alias, ?APP_ALIAS}, {dictionary, Dict}, - {module, ?MODULE}, + {module, #diameter_callback{peer_up = false, + peer_down = false, + handle_error = false, + default = ?MODULE}}, {answer_errors, callback}]}]). -define(SUCCESS, 2001). @@ -118,21 +115,17 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [start, start_services, connect] - ++ tc() - ++ [{group, all}, - disconnect, - stop_services, - stop]. + [start, + start_services, + connect, + {group, all}, + {group, all, [parallel]}, + disconnect, + stop_services, + stop]. groups() -> - [{all, [parallel], tc()}]. - -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. + [{all, [], tc()}]. %% Traffic cases run when services are started and connections %% established. @@ -249,23 +242,13 @@ call(Server) -> call(Req, Opts) -> diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). - + set([H|T], Vs) -> [H | Vs ++ T]. %% =========================================================================== %% diameter callbacks -%% peer_up/3 - -peer_up(_SvcName, _Peer, State) -> - State. - -%% peer_down/3 - -peer_down(_SvcName, _Peer, State) -> - State. - %% pick_peer/4 pick_peer([Peer | _], _, Svc, _State) @@ -309,11 +292,6 @@ handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. -%% handle_error/4 - -handle_error(Reason, _Req, _Svc, _Peer) -> - {error, Reason}. - %% handle_request/3 handle_request(Pkt, OH, {_Ref, #diameter_caps{origin_host = {OH,_}} = Caps}) diff --git a/lib/diameter/test/diameter_stats_SUITE.erl b/lib/diameter/test/diameter_stats_SUITE.erl index e50a0050a6..e7807fd360 100644 --- a/lib/diameter/test/diameter_stats_SUITE.erl +++ b/lib/diameter/test/diameter_stats_SUITE.erl @@ -26,8 +26,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_suite/1, end_per_suite/1]). @@ -44,21 +42,16 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [{group, all} | tc()]. + [{group, all}, + {group, all, [parallel]}]. groups() -> - [{all, [parallel], tc()}]. + [{all, [], tc()}]. tc() -> [an, twa]. -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. - init_per_suite(Config) -> ok = diameter:start(), Config. diff --git a/lib/diameter/test/diameter_sync_SUITE.erl b/lib/diameter/test/diameter_sync_SUITE.erl index 84f77b6066..ab629fb1c1 100644 --- a/lib/diameter/test/diameter_sync_SUITE.erl +++ b/lib/diameter/test/diameter_sync_SUITE.erl @@ -26,8 +26,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_suite/1, end_per_suite/1]). @@ -48,10 +46,11 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [{group, all} | tc()]. + [{group, all}, + {group, all, [parallel]}]. groups() -> - [{all, [parallel], tc()}]. + [{all, [], tc()}]. tc() -> [call, @@ -59,12 +58,6 @@ tc() -> timeout, flush]. -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. - init_per_suite(Config) -> ok = diameter:start(), Config. diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl index 127e3435dc..85b953dc1a 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_SUITE.erl @@ -36,8 +36,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_suite/1, end_per_suite/1]). @@ -58,18 +56,13 @@ stop_ssl/1]). %% diameter callbacks --export([peer_up/3, - peer_down/3, - pick_peer/4, - prepare_request/3, +-export([prepare_request/3, prepare_retransmit/3, handle_answer/4, - handle_error/4, handle_request/3]). -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== @@ -105,7 +98,11 @@ {'Auth-Application-Id', [Dict:id()]}, {application, [{alias, ?APP_ALIAS}, {dictionary, Dict}, - {module, ?MODULE}, + {module, #diameter_callback{peer_up = false, + peer_down = false, + pick_peer = false, + handle_error = false, + default = ?MODULE}}, {answer_errors, callback}]}]). %% Config for diameter:add_transport/2. In the listening case, listen @@ -137,31 +134,33 @@ all() -> start_diameter, make_certs, start_services, - add_transports] - ++ [{group, N} || {N, _, _} <- groups()] - ++ [remove_transports, stop_services, stop_diameter, stop_ssl]. + add_transports, + {group, all}, + {group, all, [parallel]}, + remove_transports, + stop_services, + stop_diameter, + stop_ssl]. groups() -> - Ts = tc(), - [{all, [], Ts}, - {p, [parallel], Ts}]. - -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. + [{all, [], tc()}]. +%% Shouldn't really have to know about crypto here but 'ok' from +%% ssl:start() isn't enough to guarantee that TLS is available. init_per_suite(Config) -> - case os:find_executable("openssl") of - false -> - {skip, no_openssl}; - _ -> - Config + try + false /= os:find_executable("openssl") + orelse throw({?MODULE, no_openssl}), + ok == (catch crypto:start()) + orelse throw({?MODULE, no_crypto}), + Config + catch + {?MODULE, E} -> + {skip, E} end. end_per_suite(_Config) -> - ok. + crypto:stop(). %% Testcases to run when services are started and connections %% established. @@ -246,21 +245,6 @@ send5(_Config) -> %% =========================================================================== %% diameter callbacks -%% peer_up/3 - -peer_up(_SvcName, _Peer, State) -> - State. - -%% peer_down/3 - -peer_down(_SvcName, _Peer, State) -> - State. - -%% pick_peer/4 - -pick_peer([Peer], _, ?CLIENT, _State) -> - {ok, Peer}. - %% prepare_request/3 prepare_request(#diameter_packet{msg = Req}, @@ -285,11 +269,6 @@ handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. -%% handle_error/4 - -handle_error(Reason, _Req, ?CLIENT, _Peer) -> - {error, Reason}. - %% handle_request/3 handle_request(#diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId}}, diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 55c5fc7c54..6eed8d3b5d 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -89,7 +89,6 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== @@ -163,27 +162,16 @@ suite() -> [{timetrap, {seconds, 10}}]. all() -> - [start, start_services, add_transports, result_codes - | [{group, N} || {N, _, _} <- groups()]] + [start, start_services, add_transports, result_codes] + ++ [{group, E, P} || E <- ?ENCODINGS, P <- [[], [parallel]]] ++ [remove_transports, stop_services, stop]. groups() -> Ts = tc(), - [{grp(E,P), P, Ts} || E <- ?ENCODINGS, P <- [[], [parallel]]]. - -grp(E, []) -> - E; -grp(E, [parallel]) -> - ?P(E). + [{E, [], Ts} || E <- ?ENCODINGS]. init_per_group(Name, Config) -> - E = case ?L(Name) of - "p_" ++ Rest -> - ?A(Rest); - _ -> - Name - end, - [{encode, E} | Config]. + [{encode, Name} | Config]. end_per_group(_, _) -> ok. @@ -517,10 +505,10 @@ send_multiple_filters(Config, Fs) -> %% Ensure that we can pass a request in any form to diameter:call/4, %% only the return value from the prepare_request callback being %% significant. -send_anything(Config) -> +send_anything(Config) -> #diameter_base_STA{'Result-Code' = ?SUCCESS} = call(Config, anything). - + %% =========================================================================== call(Config, Req) -> diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl index c22adc3334..893b7ba2f9 100644 --- a/lib/diameter/test/diameter_transport_SUITE.erl +++ b/lib/diameter/test/diameter_transport_SUITE.erl @@ -27,8 +27,6 @@ -export([suite/0, all/0, groups/0, - init_per_group/2, - end_per_group/2, init_per_suite/1, end_per_suite/1]). @@ -46,7 +44,6 @@ -include_lib("kernel/include/inet_sctp.hrl"). -include("diameter.hrl"). --include("diameter_ct.hrl"). -define(util, diameter_util). @@ -93,10 +90,13 @@ suite() -> [{timetrap, {minutes, 2}}]. all() -> - [start | tc()] ++ [{group, all}, stop]. + [start, + {group, all}, + {group, all, [parallel]}, + stop]. groups() -> - [{all, [parallel], tc()}]. + [{all, [], tc()}]. tc() -> [tcp_accept, @@ -104,12 +104,6 @@ tc() -> sctp_accept, sctp_connect]. -init_per_group(_, Config) -> - Config. - -end_per_group(_, _) -> - ok. - init_per_suite(Config) -> [{sctp, have_sctp()} | Config]. @@ -176,14 +170,12 @@ connect(Prot) -> %% have_sctp/0 have_sctp() -> - try gen_sctp:open() of + case gen_sctp:open() of {ok, Sock} -> gen_sctp:close(Sock), true; - {error, eprotonosupport} -> %% fail on any other reason - false - catch - error: badarg -> + {error, E} when E == eprotonosupport; + E == esocktnosupport -> %% fail on any other reason false end. @@ -220,7 +212,7 @@ init(gen_connect, {Prot, Ref}) -> [PortNr] = ?util:lport(Prot, Ref, 20), %% Connect, send a message and receive it back. - {ok, Sock} = gen_connect(Prot, PortNr, Ref), + {ok, Sock} = gen_connect(Prot, PortNr), Bin = make_msg(), ok = gen_send(Prot, Sock, Bin), Bin = gen_recv(Prot, Sock); @@ -359,20 +351,7 @@ tmod(tcp) -> %% =========================================================================== -%% gen_connect/3 - -gen_connect(Prot, PortNr, Ref) -> - Pid = sync(connect, Ref), - - %% Stagger connect attempts to avoid the situation that no - %% transport process is accepting yet. - receive after 250 -> ok end, - - try - gen_connect(Prot, PortNr) - after - Pid ! Ref - end. +%% gen_connect/2 gen_connect(sctp = P, PortNr) -> {ok, Sock} = Ok = gen_sctp:open([{ip, ?ADDR}, {port, 0} | ?SCTP_OPTS]), diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 6b1dc1f0c9..0c42f955ad 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -212,7 +212,7 @@ read_priv(Config, Name) -> read(Path) -> {ok, Bin} = file:read_file(Path), binary_to_term(Bin). - + %% map_priv/3 %% %% Modify a term in a file and return both old and new values. diff --git a/lib/diameter/test/diameter_watchdog_SUITE.erl b/lib/diameter/test/diameter_watchdog_SUITE.erl index b40d7c104d..ff40326947 100644 --- a/lib/diameter/test/diameter_watchdog_SUITE.erl +++ b/lib/diameter/test/diameter_watchdog_SUITE.erl @@ -306,11 +306,8 @@ watchdog(Type, Ref, TPid, Wd) -> Opts = [{transport_module, ?MODULE}, {transport_config, TPid}, {watchdog_timer, Wd}], - monitor(diameter_watchdog:start({Type, Ref}, - {false, Opts, false, ?SERVICE})). - -monitor(Pid) -> - erlang:monitor(process, Pid), + {_MRef, Pid} = diameter_watchdog:start({Type, Ref}, + {false, Opts, false, ?SERVICE}), Pid. %% =========================================================================== @@ -350,6 +347,10 @@ init(_, _, TPid, _) -> monitor(TPid), 3. +monitor(Pid) -> + erlang:monitor(process, Pid), + Pid. + %% Generate a unique hostname for the faked peer. hostname() -> lists:flatten(io_lib:format("~p-~p-~p", tuple_to_list(now()))). diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index f88258c232..7f163536fb 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -24,6 +24,7 @@ MODULES = \ diameter_ct \ diameter_util \ diameter_enum \ + diameter_compiler_SUITE \ diameter_codec_SUITE \ diameter_codec_test \ diameter_app_SUITE \ @@ -32,6 +33,7 @@ MODULES = \ diameter_sync_SUITE \ diameter_stats_SUITE \ diameter_watchdog_SUITE \ + diameter_gen_sctp_SUITE \ diameter_transport_SUITE \ diameter_capx_SUITE \ diameter_traffic_SUITE \ @@ -41,3 +43,9 @@ MODULES = \ HRL_FILES = \ diameter_ct.hrl + +DATA = \ + diameter_codec_SUITE_data/avps.dia \ + diameter_codec_SUITE_data/send.dia \ + diameter_codec_SUITE_data/recv.dia \ + diameter_codec_SUITE_data/diameter_test_unknown.erl diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index b1d3ba2241..0c240798cc 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 0.11 +DIAMETER_VSN = 1.0 PRE_VSN = APP_VSN = "$(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)" |