From 3cdd095e8de8506881a9856e711a90c9ed723f0a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 17 Nov 2011 09:33:51 +0100 Subject: Minor documentation fixes --- lib/diameter/doc/src/diameter_dict.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index e7c530f1b8..01616f519d 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -44,9 +44,9 @@ A diameter service as configured with diameter:start_service/2 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 FILE FORMAT below. Users add support for their specific applications by creating @@ -56,7 +56,7 @@ resulting dictionaries modules on a service.

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 diameter_app(3).) @@ -81,7 +81,7 @@ 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.

@@ -167,7 +167,7 @@ Name must be of the same form as a @name.

A prefix is optional but can be used to disambiguate record and constant names -resulting from similarly named messages and AVP's in different +resulting from similarly named messages and AVPs in different Diameter applications.

@@ -182,7 +182,7 @@ Example:

@vendor Number Name

-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. @@ -200,7 +200,7 @@ Example:

@avp_vendor_id Number

-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 @vendor default. The section content consists of AVP names. Can occur zero or more times (with different values of Number).

@@ -254,7 +254,7 @@ 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 DATA TYPES below or a -type as defined by a @custom_types tag.

+type as defined by a @custom_types section.

Example:

@@ -277,11 +277,11 @@ to 0 as mandated by the current draft standard.

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.

+The section contents consists of AVP names. +For each AVP Name in the content, Mod:Name(encode|decode, Type, Data) +is expected to provide encode/decode for values of the AVP, where Type +is the type of the AVP as declared in the @avp_types section +of the dictionary and Data is the value to encoded/decoded.

Can occur 0 or more times (with different values of Mod).

@@ -290,7 +290,7 @@ Can occur 0 or more times (with different values of Mod).

Example:

-@custom_types rfc4005_types +@custom_types rfc4005_avps Framed-IP-Address -- cgit v1.2.3 From ca185011269606596814075d4c8f9d13a855866b Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Oct 2011 21:36:37 +0200 Subject: Replace dictionary file parser The previous parse was very adhoc and simply crashed on any kind of input error, providing no identification of the objectionable input that caused the parse to fail. The new parser is generated from a yecc grammar, making it easier both to understand what it is that's being parsed and to provide useful diagnostics to the user in case of error. --- lib/diameter/src/Makefile | 34 ++- lib/diameter/src/compiler/diameter_dict_parser.yrl | 324 +++++++++++++++++++++ .../src/compiler/diameter_dict_scanner.erl | 265 +++++++++++++++++ lib/diameter/src/compiler/diameter_spec_scan.erl | 157 ---------- lib/diameter/src/gen/.gitignore | 2 +- lib/diameter/src/modules.mk | 6 +- 6 files changed, 619 insertions(+), 169 deletions(-) create mode 100644 lib/diameter/src/compiler/diameter_dict_parser.yrl create mode 100644 lib/diameter/src/compiler/diameter_dict_scanner.erl delete mode 100644 lib/diameter/src/compiler/diameter_spec_scan.erl (limited to 'lib/diameter') diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index eea2aa894d..2ec016ecbc 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,11 +76,12 @@ 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) @@ -125,6 +128,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 +153,8 @@ info: @echo ======================================== @$(call list,DICTS) @echo + @$(call list,DICT_YRL) + @echo @$(call list,RT_MODULES) @echo @$(call list,CT_MODULES) @@ -164,7 +173,7 @@ info: @echo ======================================== clean: - rm -f $(TARGET_FILES) $(DICT_ERLS) $(DICT_HRLS) + rm -f $(TARGET_FILES) gen/* rm -f depend.mk # ---------------------------------------------------- @@ -192,8 +201,9 @@ release_spec: opt $(MAKE) $(TARGET_DIRS:%/=release_src_%) $(TARGET_DIRS:%/=release_src_%): release_src_%: - $(INSTALL_DATA) $(filter $*/%,$(TARGET_MODULES:%=%.erl) \ - $(INTERNAL_HRLS)) \ + $(INSTALL_DATA) $(filter $*/%, $(TARGET_MODULES:%=%.erl) \ + $(INTERNAL_HRLS)) \ + $(filter $*/%, compiler/$(DICT_YRL).yrl) \ $(RELSYSDIR)/src/$* release_docs_spec: @@ -207,7 +217,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,11 +234,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_%) +# Keep intermediate files. +.SECONDARY: $(DICT_ERLS) $(DICT_HRLS) gen/$(DICT_YRL:%=%.erl) + # ---------------------------------------------------- # Targets using secondary expansion (make >= 3.81) # ---------------------------------------------------- @@ -237,4 +249,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/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 = "::=" 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 = "::=" 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..74bf0cb06a --- /dev/null +++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl @@ -0,0 +1,265 @@ +%% +%% %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]). + +%% ----------------------------------------------------------- +%% # scan/1 +%% ----------------------------------------------------------- + +-spec scan(string()) -> {ok, [Token]} | {error, {atom(), 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({Reason, Input, Lineno}) -> + io_lib:format("~s at line ~p: ~s", + [Reason, Lineno, head(Input, [], 20, true)]). + +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 splitwith(fun(C) -> not lists:member(C, "'\r\n") end, T) of + {[_|_] = A, [$'|Rest]} -> + {{word, A}, Rest}; + {[_|_], _} -> %% not terminated on same line + "Unterminated atom"; + {[], []} -> %% last character + "Unterminated atom"; + {[], _} -> + "Empty atom" + 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_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/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..6929528a37 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -24,6 +24,10 @@ 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 \ @@ -62,7 +66,7 @@ CT_MODULES = \ base/diameter_info \ compiler/diameter_codegen \ compiler/diameter_exprecs \ - compiler/diameter_spec_scan \ + compiler/diameter_dict_scanner \ compiler/diameter_spec_util \ compiler/diameter_make -- cgit v1.2.3 From 371ec40cd3bbee34d954a28e5bdf86098e619ed4 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 16 Nov 2011 16:58:34 +0100 Subject: diameter_spec_util -> diameter_dict_util and adapt to parser Errors are now detected after the parse with format_error/1 providing understandable error messages, pointing to the offending line number(s) in the dictionary source. --- lib/diameter/src/compiler/diameter_dict_util.erl | 1108 ++++++++++++++++++++++ lib/diameter/src/compiler/diameter_forms.hrl | 7 + lib/diameter/src/compiler/diameter_spec_util.erl | 1089 --------------------- lib/diameter/src/compiler/diameter_vsn.hrl | 22 + lib/diameter/src/modules.mk | 5 +- 5 files changed, 1140 insertions(+), 1091 deletions(-) create mode 100644 lib/diameter/src/compiler/diameter_dict_util.erl delete mode 100644 lib/diameter/src/compiler/diameter_spec_util.erl create mode 100644 lib/diameter/src/compiler/diameter_vsn.hrl (limited to 'lib/diameter') 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..7c992316f9 --- /dev/null +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -0,0 +1,1108 @@ +%% +%% %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]). + +-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). + +%% =========================================================================== +%% parse/2 +%% =========================================================================== + +-spec parse(File, Opts) + -> {ok, orddict:orddict()} + | {error, term()} + when File :: {path, string()} + | string() + | binary(), + Opts :: list(). + +parse(File, Opts) -> + put({?MODULE, verbose}, lists:member(verbose, Opts)), + try + {ok, do_parse(File, Opts)} + catch + {Reason, ?MODULE, _Line} -> + {error, Reason} + after + erase({?MODULE, 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 ", integer_to_list(Line), ", ", Reason]); + +format_error(T) -> + {Fmt, As} = fmt(T), + lists:flatten(io_lib:format(Fmt, As)). + +fmt({avp_code_already_defined, [Code, false, Name, Line, L]}) -> + {"AVP ~p (~s) at line ~p already defined at line ~p", + [Code, Name, Line, L]}; + +fmt({Reason, As}) -> + {fmt(Reason), As}; + +fmt(avp_code_already_defined) -> + "AVP ~p/~p (~s) at line ~p already defined at line ~p"; + +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(invalid_qualifier) -> + "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(beam_not_on_path) -> + "Module ~s not found"; + +fmt(recompile) -> + "Module ~p appears to have been compiler with an incompatible " + "version of the dictionary compiler and must be recompiled"; +fmt(no_dict) -> + "Module ~p does not appear to be a diameter dictionary". + +%% =========================================================================== +%% =========================================================================== + +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, 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]) -> + {qual(Q), avp(D, Avp)}. + +avp(D, {'AVP', _}) -> + delim(D, "AVP"); +avp(D, {_, _, Name}) -> + delim(D, Name). + +delim($<, N) -> + {{N}}; +delim(${, N) -> + {N}; +delim($[, N) -> + [N]. + +qual({true, {_,_,N}}) -> + {'*', N}; +qual({{_,_,N}, true}) -> + {N, '*'}; +qual({{_,_,N},{_,_,M}}) -> + {N, M}; +qual(true) -> + '*'. + +%% Optional reports when running verbosely. +report(What, [F | A]) + when is_function(F) -> + report(What, apply(F, A)); +report(What, Data) -> + report(get({?MODULE, 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) -> + dict:append(Key, [0, {word, 0, Mod}], Dict); +opt({Key, Val}, Dict) -> + dict:store(Key, [0, {word, 0, Val}], Dict); +opt(_, Dict) -> + Dict. + +%% =========================================================================== +%% 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, + [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, 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}, 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}], Dict, {_, _, X}, K) -> + store_new({key(K), {X, Key}}, + [Line, Value, K], + Dict, + [X, 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. + Dict1 = store_new({K, Name}, + [Line | Toks], + Dict0, + [Name, Line], + avp_name_already_defined), + + [{number, _, _Code}, {word, _, Type}, {word, _, _Flags}] = Toks, + + true = avp_type_known(Type, Name, Line), + + Dict1; + +%% {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}, Vid] = Header, + {DefLine, {_, _, Flags}} = grouped_flags(Name, Code, Dict0, Line), + V = lists:member($V, Flags), + + 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}, Bits, ApplId] = Header, + + %% 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|_], [[{{_, Line, Min}, {_, _, Max}}, D, _] | _], _, _, _) + when Min > Max -> + ?RETURN(invalid_qualifier, [Min, Max, 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($[) -> $]. + +%% 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 + +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, Options) -> + Path = [D || {include, D} <- Options] + ++ [".", code:lib_dir(diameter, ebin)], + foldl([fun find_avps/3, Path], [], find(inherits, Dict)). +%% Note that the module order of the returned lists is reversed +%% relative to @inherits. + +find_avps([Line, {_,_,M} | Names], Acc, Path) -> + Mod = ?A(M), + report(inherit_from, Mod), + Avps = avps_from_beam(find_beam(Mod, Path), Mod), %% could be empty + case find_avps(Names, Avps) 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 fa/2, {[], Names}, Avps). + +fa({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. + +%% 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 -> + ?RETURN(beam_not_on_path, [Mod]) + 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, dict(Mod)). + +load_module(false, Mod, Path) -> + R = filename:rootname(Path, code:objfile_extension()), + {module, Mod} = code:load_abs(R), + ok; +load_module({file, _}, _, _) -> + ok. + +dict(Mod) -> + try Mod:dict() of + [?VERSION | Dict] -> + Dict; + _ -> + ?RETURN(recompile, [Mod]) + catch + error:_ -> + ?RETURN(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. + +%% =========================================================================== + +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_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_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/modules.mk b/lib/diameter/src/modules.mk index 6929528a37..b86a32a4f7 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -67,7 +67,7 @@ CT_MODULES = \ compiler/diameter_codegen \ compiler/diameter_exprecs \ compiler/diameter_dict_scanner \ - compiler/diameter_spec_util \ + compiler/diameter_dict_util \ compiler/diameter_make # Released hrl files in ../include intended for public consumption. @@ -79,7 +79,8 @@ EXTERNAL_HRLS = \ 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 = \ -- cgit v1.2.3 From 44495f2bdfa6c1d68bb27ba3ab6c31d8dea81519 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 15:17:34 +0100 Subject: Adapt diameter_codegen --- lib/diameter/src/compiler/diameter_codegen.erl | 256 ++++++++++++------------- 1 file changed, 125 insertions(+), 131 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 0fd4a0b301..a38cc42df2 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,16 @@ 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) -> +%% =========================================================================== + +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 +73,7 @@ getr(Key) -> %% =========================================================================== %% =========================================================================== -%% Generate from parsed spec in a file. +%% Generate from parsed dictionary in a file. file(F) -> file(F, spec). @@ -83,7 +83,7 @@ file(F, Mode) -> file(F, Outdir, Mode) -> {ok, [Spec]} = file:consult(F), - from_spec(F, Spec, Outdir, Mode). + from_dict(F, Spec, Outdir, Mode). %% =========================================================================== %% =========================================================================== @@ -108,13 +108,13 @@ w(Path, Spec, Fmt) -> 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); @@ -124,11 +124,9 @@ gen(hrl, Spec, Mod, Path) -> gen(erl = Mode, Spec, Mod, Path) when is_list(Mod) -> - gen(Mode, Spec, list_to_atom(Mod), Path); + gen(Mode, Spec, ?A(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]}, @@ -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,7 +254,7 @@ c_id({ok, Id}) -> {?clause, [], [], [?INTEGER(Id)]}; c_id(error) -> - ?UNEXPECTED(0). + ?BADARG(0). %%% ------------------------------------------------------------------------ %%% # vendor_id/0 @@ -274,7 +272,7 @@ f_vendor_id(Spec) -> f_vendor_name(Spec) -> {_, Name} = orddict:fetch(vendor, Spec), {?function, vendor_name, 0, - [{?clause, [], [], [?ATOM(Name)]}]}. + [{?clause, [], [], [?Atom(Name)]}]}. %%% ------------------------------------------------------------------------ %%% # msg_name/1 @@ -294,15 +292,10 @@ msg_name(Spec) -> 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 +306,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 +322,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 +338,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. @@ -399,8 +373,8 @@ avp_name(Spec) -> lists:map(fun(T) -> c_avp_name(T, Vid, Vs) end, Avps) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('AVP')]}]. -c_avp_name({Name, Code, Type, Flags, _Encr}, Vid, Vs) -> - c_avp_name({Name, Type}, +c_avp_name({Name, Code, Type, Flags}, Vid, Vs) -> + c_avp_name({?A(Name), ?A(Type)}, Code, lists:member('V', Flags), Vid, @@ -445,60 +419,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). - report(native, Dict), +types(Avps) -> + lists:map(fun({N,_,T,_}) -> {N,T} end, Avps). + +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)]}. -base_avp(AvpName, 'Enumerated') -> - ?CALL(enumerated_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]); +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')]); +base_avp(AvpName, "Grouped") -> + ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); base_avp(_, Type) -> - ?APPLY(diameter_types, Type, [?VAR('T'), ?VAR('Data')]). + ?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 +495,39 @@ 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) -> + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], - [?APPLY(Mod, AvpName, [?VAR('T'), ?ATOM(Type), ?VAR('Data')])]}. + [custom(Mod, Key, AvpName, Type)]}. + +custom(Mod, custom_types, AvpName, Type) -> + ?APPLY(?A(Mod), ?A(AvpName), [?VAR('T'), ?Atom(Type), ?VAR('Data')]); +custom(Mod, codecs, AvpName, Type) -> + ?APPLY(?A(Mod), ?A(Type), [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]). %%% ------------------------------------------------------------------------ %%% # 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 +548,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(<>)], +c_enumerated_avp(AvpName, {_,I}) -> + [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<>)], [], [?TERM(I)]}, - {?clause, [?ATOM(encode), ?ATOM(AvpName), ?INTEGER(I)], + {?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)], [], [?TERM(<>)]}]. @@ -567,7 +561,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 +576,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,7 +592,7 @@ 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), @@ -610,17 +604,17 @@ avp_header(Spec) -> lists:flatmap(fun(A) -> c_avp_header({Vid, Vs}, A) end, Native ++ Imported). -c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags, _Encr}) -> - [{?clause, [?ATOM(Name)], +c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags}) -> + [{?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(Vs, Mod, {Name, _, _, Flags, _}) -> - Apply = ?APPLY(Mod, avp_header, [?ATOM(Name)]), - {?clause, [?ATOM(Name)], +c_avp_header(Vs, Mod, {Name, _Code, _Type, Flags}) -> + Apply = ?APPLY(Mod, avp_header, [?Atom(Name)]), + {?clause, [?Atom(Name)], [], [case proplists:get_value(Name, Vs) of undefined -> @@ -633,9 +627,9 @@ c_avp_header(Vs, Mod, {Name, _, _, Flags, _}) -> 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). @@ -656,19 +650,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 +672,7 @@ c_empty_value({Name, _}) -> f_dict(Spec) -> {?function, dict, 0, - [{?clause, [], [], [?TERM(Spec)]}]}. + [{?clause, [], [], [?TERM([?VERSION | Spec])]}]}. %%% ------------------------------------------------------------------------ %%% # gen_hrl/3 @@ -706,10 +700,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 +745,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,15 +788,15 @@ 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)} @@ -818,10 +812,10 @@ arity(T) -> T. 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. -- cgit v1.2.3 From 8d5517720dfa028aaad1d236fe39fbebfc013bc3 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 02:41:08 +0100 Subject: Always modify code with diameter_exprecs A dictionary need define neither messages nor grouped AVPs, in which case no record definitions are generated. However, the generated module still includes diameter_gen.hrl and this requires some functions diameter_exprecs would otherwise insert, even if the code that uses these will not be called. --- lib/diameter/src/compiler/diameter_exprecs.erl | 44 ++++++-------------------- 1 file changed, 9 insertions(+), 35 deletions(-) (limited to 'lib/diameter') 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')}], -- cgit v1.2.3 From 34c0b1642a49626efb90762f69bdf195ff9197bd Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 28 Nov 2011 23:04:55 +0100 Subject: Adapt diameter_make --- lib/diameter/src/compiler/diameter_make.erl | 62 +++++++++++++++++------------ 1 file changed, 37 insertions(+), 25 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl index 5380ee56ca..a9195ba2d1 100644 --- a/lib/diameter/src/compiler/diameter_make.erl +++ b/lib/diameter/src/compiler/diameter_make.erl @@ -20,20 +20,20 @@ %% %% Module alternative to diameterc for dictionary compilation. %% -%% Eg. 1> diameter_make:dict("mydict.dia"). +%% Eg. 1> diameter_make:file("mydict.dia"). %% %% $ erl -noshell \ %% -boot start_clean \ -%% -s diameter_make dict mydict.dia \ +%% -s diameter_make file mydict.dia \ %% -s init stop %% -module(diameter_make). --export([dict/1, - dict/2, - spec/1, - spec/2]). +-export([file/1, + file/2, + dict/1, + dict/2]). -type opt() :: {outdir|include|name|prefix|inherits, string()} | verbose @@ -41,37 +41,49 @@ %% dict/1-2 --spec dict(string(), [opt()]) - -> ok. +-spec file(string(), [opt()]) + -> ok + | {error, string()}. -dict(File, Opts) -> - make(File, - Opts, - spec(File, Opts), - [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]). +file(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. -dict(File) -> - dict(File, []). +file(File) -> + file(File, []). -%% spec/2 +%% dict/2 --spec spec(string(), [opt()]) - -> orddict:orddict(). +-spec dict(string(), [opt()]) + -> {ok, orddict:orddict()} + | {error, string()}. -spec(File, Opts) -> - diameter_spec_util:parse(File, Opts). +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. -spec(File) -> - spec(File, []). +dict(File) -> + dict(File, []). %% =========================================================================== make(_, _, _, []) -> ok; -make(File, Opts, Spec, [Mode | Rest]) -> - try diameter_codegen:from_spec(File, Spec, Opts, Mode) of +make(File, Opts, Dict, [Mode | Rest]) -> + try diameter_codegen:from_dict(File, Dict, Opts, Mode) of ok -> - make(File, Opts, Spec, Rest) + make(File, Opts, Dict, Rest) catch error: Reason -> {error, {Reason, Mode, erlang:get_stacktrace()}} -- cgit v1.2.3 From 0499cca4817c677a4b75a1280d7d51c10263c224 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 02:46:20 +0100 Subject: Adapt diameterc --- lib/diameter/bin/diameterc | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'lib/diameter') 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; -- cgit v1.2.3 From 9ab45a829c18820dbc44243a48d66652ea13bb70 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 23:10:38 +0100 Subject: No longer inherit common dictionary in relay dictionary Base AVPs used for relaying and statistics are always taken from the common dictionary. This is consistent with the way that Route-Record is handled for one, and statistics should not rely on the dictionary of any specific application. --- lib/diameter/src/base/diameter_service.erl | 38 ++++++++++++++---------------- lib/diameter/src/dict/relay.dia | 2 -- 2 files changed, 18 insertions(+), 22 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 7adcf1c265..8b9314ad1c 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -1490,7 +1490,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 +1946,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 +2218,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 +2253,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 +2271,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 +2289,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 +2353,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 +2466,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 +2507,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/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 -- cgit v1.2.3 From 943266c923535a22050c18211d9f4ef95919aebc Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 14:29:39 +0100 Subject: Vendor id fixes @vendor is only required if the id is actually needed. That is, if there is a locally defined AVP whose V flag is set and which does not have a vendor id set by @avp_vendor_id. Also, in the case of an inherited AVP, fix avp_name/2 in a generated dictionary module defaulting vendor id from @vendor in the inheriting dictionary but avp_header/1 defaulting it from the inherited dictionary. In both cases the vendor id now defaults from @vendor in the inherited dictionary. Note that @avp_vendor_id from the inherited dictionary is ignored: any changes from @vendor have to be explicit in the inheriting dictionary. A better alternative to @avp_vendor_id is to simply inherit from dictionaries setting the appropriate @vendor but this was previously somewhat broken so @avp_vendor_id was needed to set the id of an AVP whose definition was copied from another source into a dictionary that only inherited from the common dictionary (which doesn't set V on any AVPs). --- lib/diameter/src/compiler/diameter_codegen.erl | 92 ++++++++++++++++---------- 1 file changed, 58 insertions(+), 34 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index a38cc42df2..b3af326049 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -88,9 +88,6 @@ file(F, Outdir, Mode) -> %% =========================================================================== %% =========================================================================== -choose(true, X, _) -> X; -choose(false, _, X) -> X. - get_value(Key, Plist) -> proplists:get_value(Key, Plist, []). @@ -261,18 +258,26 @@ c_id(error) -> %%% ------------------------------------------------------------------------ f_vendor_id(Spec) -> - {Id, _} = orddict:fetch(vendor, Spec), {?function, vendor_id, 0, - [{?clause, [], [], [?INTEGER(Id)]}]}. + [{?clause, [], [], [vendor_id(orddict:find(vendor, Spec))]}]}. + +vendor_id({ok, {Id, _}}) -> + ?INTEGER(Id); +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, [], [], [vendor_name(orddict:find(vendor, Spec))]}]}. + +vendor_name({ok, {_, Name}}) -> + ?Atom(Name); +vendor_name(error) -> + ?APPLY(erlang, error, [?TERM(undefined)]). %%% ------------------------------------------------------------------------ %%% # msg_name/1 @@ -364,32 +369,39 @@ 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), + Avps = get_value(avp_types, Spec), + Imported = get_value(import_avps, Spec), + Vid = orddict:find(vendor, Spec), + Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, get_value(avp_vendor_id, 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}, Vid, Vs) -> - c_avp_name({?A(Name), ?A(Type)}, - Code, - lists:member('V', Flags), - Vid, - proplists:get_value(Name, Vs)). - -c_avp_name(T, Code, false, _, undefined = U) -> - {?clause, [?INTEGER(Code), ?ATOM(U)], +c_avp_name({Name, Code, Type, Flags}, Vs, Vid) -> + c_avp_name_(?TERM({?A(Name), ?A(Type)}), + ?INTEGER(Code), + vid(Name, Flags, Vs, Vid)). + +%% 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. +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]}. %%% ------------------------------------------------------------------------ %%% # avp_arity/2 @@ -597,22 +609,26 @@ f_avp_header(Spec) -> avp_header(Spec) -> Native = get_value(avp_types, Spec), Imported = get_value(import_avps, Spec), - {Vid, _} = orddict:fetch(vendor, Spec), + Vid = orddict:find(vendor, Spec), + Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, get_value(avp_vendor_id, 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}) -> +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, _Code, _Type, Flags}) -> +%% 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)], [], @@ -620,7 +636,6 @@ c_avp_header(Vs, Mod, {Name, _Code, _Type, Flags}) -> undefined -> Apply; Vid -> - true = lists:member('V', Flags), %% sanity check ?CALL(setelement, [?INTEGER(3), Apply, ?INTEGER(Vid)]) end]}. @@ -632,10 +647,19 @@ 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. -- cgit v1.2.3 From b335e57a6dacc9111a9fc44dcbdf7210c167653d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 23:45:09 +0100 Subject: Update codec suite --- lib/diameter/test/diameter_codec_test.erl | 60 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) (limited to 'lib/diameter') 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 = <>, 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()). -- cgit v1.2.3 From 797fcb34fdb46972a5f2808cdea1f28703729a0d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 1 Dec 2011 02:56:01 +0100 Subject: Update app suite --- lib/diameter/test/diameter_app_SUITE.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 7f53a4ddd4..88bd542eb8 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_SUITE.erl @@ -96,11 +96,12 @@ modules(Config) -> Help = [diameter_callback, diameter_codegen, diameter_dbg, + diameter_dict_parser, + diameter_dict_scanner, + diameter_dict_util, diameter_exprecs, diameter_info, - diameter_make, - diameter_spec_scan, - diameter_spec_util], + diameter_make], {[], Help} = {Mods -- Installed, lists:sort(Installed -- Mods)}. code_mods() -> -- cgit v1.2.3 From 57a4441bea845c42235d6c88523216f2a379baa0 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 29 Nov 2011 09:46:49 +0100 Subject: Add compiler suite --- lib/diameter/test/diameter_compiler_SUITE.erl | 256 ++++++++++++++++++++++++++ lib/diameter/test/modules.mk | 1 + 2 files changed, 257 insertions(+) create mode 100644 lib/diameter/test/diameter_compiler_SUITE.erl (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl new file mode 100644 index 0000000000..7caea3258e --- /dev/null +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -0,0 +1,256 @@ +%% +%% %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). +-compile({no_auto_import, [error/2]}). + +-export([suite/0, + all/0, + init_per_suite/1, + end_per_suite/1]). + +%% testcases +-export([base/1, + replace/1, replace/2]). + +-export([dict/0]). %% fake dictionary module + +-define(base, "base_rfc3588.dia"). +-define(util, diameter_util). +-define(S, atom_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, + [{scan, + "@id 0", + "@id \\&"}, + {parse, + "@id 0", + "@id @id"}, + {avp_code_already_defined, + "480", + "485"}, + {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&"}]}, + {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} &"}, + {invalid_qualifier, + "CEA ::=.*", + "& 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 ::=", " &"}]}, + {enumerated_avp_has_wrong_local_type, + "Enumerated", + "Time"}, + {enumerated_avp_not_defined, + [{"{ Disconnect-Cause }", ""}, + {"^ *Disconnect-Cause .*", ""}]}, + {avp_not_defined, + "CEA ::=", + " &"}, + {beam_not_on_path, + [{"@avp_types", "@inherits nomod XXX &"}, + {"CEA ::=", " &"}]}, + {recompile, + [{"@avp_types", "@inherits " ++ ?S(?MODULE) ++ " XXX &"}, + {"CEA ::=", " &"}]}, + {no_dict, + [{"@avp_types", "@inherits diameter XXX &"}, + {"CEA ::=", " &"}]}, + {ok, + "@avp_types", + "@end & bad syntax"}, + {parse, + "@avp_types", + "& bad syntax"}, + {ok, + [{"^ *Class .*", "@avp_types"}, + {"^ *Failed-AVP ", "@avp_types &"}, + {"@grouped", "&&"}, + {"^ *Failed-AVP ::=", "@grouped &"}, + {"CEA ::=", " &"}, + {"@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&"}]}]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 5}}]. + +all() -> + [base, + replace]. + +%% 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. + +%% =========================================================================== +%% testcases + +%% base/1 + +%% This work around a minor deficiency in diameter_dict_util, that it +%% does deal well with compiling in parallel. Load the dictionary +%% ahead of time so that compilation doesn't. +base(_Config) -> + Base = filename:join([code:lib_dir(diameter, ebin), + "diameter_gen_base_rfc3588"]), + {module, _} = code:load_abs(Base). + +%% replace/1 + +replace(Config) -> + Bin = proplists:get_value(base, Config), + [] = ?util:run([{?MODULE, [replace, E, Bin]} || E <- ?REPLACE]). + +replace({E, RE, Repl}, Bin) -> + replace({E, [{RE, Repl}]}, Bin); + +replace({E, Mods}, Bin) -> + B = iolist_to_binary(lists:foldl(fun re/2, Bin, Mods)), + case diameter_dict_util:parse(B, [{include, here()}]) of + {ok, Dict} when E == ok -> + 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]). + +%% =========================================================================== + +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/modules.mk b/lib/diameter/test/modules.mk index f88258c232..54978d820c 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 \ -- cgit v1.2.3 From 0e4fd87177dd24b1d3545b6b5f0df873a580d4ae Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 2 Dec 2011 15:06:44 +0100 Subject: Remove unnecessary includes --- lib/diameter/test/diameter_failover_SUITE.erl | 1 - lib/diameter/test/diameter_relay_SUITE.erl | 1 - lib/diameter/test/diameter_tls_SUITE.erl | 1 - lib/diameter/test/diameter_traffic_SUITE.erl | 1 - lib/diameter/test/diameter_transport_SUITE.erl | 1 - 5 files changed, 5 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index f4d62f94c6..aef2447587 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_SUITE.erl @@ -59,7 +59,6 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl index 40cbdf805a..b8e194ba9e 100644 --- a/lib/diameter/test/diameter_relay_SUITE.erl +++ b/lib/diameter/test/diameter_relay_SUITE.erl @@ -66,7 +66,6 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl index 127e3435dc..4abc7bb23f 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_SUITE.erl @@ -69,7 +69,6 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). --include("diameter_ct.hrl"). %% =========================================================================== diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 55c5fc7c54..78131b4ec4 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"). %% =========================================================================== diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl index c22adc3334..3bd96ee422 100644 --- a/lib/diameter/test/diameter_transport_SUITE.erl +++ b/lib/diameter/test/diameter_transport_SUITE.erl @@ -46,7 +46,6 @@ -include_lib("kernel/include/inet_sctp.hrl"). -include("diameter.hrl"). --include("diameter_ct.hrl"). -define(util, diameter_util). -- cgit v1.2.3 From 246470bd7492f97ca4b7528b3c1c4ed76b91d4e0 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 11:54:16 +0100 Subject: Minor codegen tweaks --- lib/diameter/src/compiler/diameter_codegen.erl | 49 ++++++++++++++------------ 1 file changed, 27 insertions(+), 22 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index b3af326049..3105310c5b 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -48,6 +48,13 @@ %% =========================================================================== +-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)), @@ -114,15 +121,11 @@ mod(_, {ok, Mod}) -> Mod. gen(spec, Spec, _Mod, Path) -> - write(Path ++ ".spec", Spec); + write(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, ?A(Mod), Path); - gen(erl, Spec, Mod, Path) -> Forms = [{?attribute, module, Mod}, {?attribute, compile, [{parse_transform, diameter_exprecs}]}, @@ -259,11 +262,11 @@ c_id(error) -> f_vendor_id(Spec) -> {?function, vendor_id, 0, - [{?clause, [], [], [vendor_id(orddict:find(vendor, Spec))]}]}. + [{?clause, [], [], [b_vendor_id(orddict:find(vendor, Spec))]}]}. -vendor_id({ok, {Id, _}}) -> +b_vendor_id({ok, {Id, _}}) -> ?INTEGER(Id); -vendor_id(error) -> +b_vendor_id(error) -> ?APPLY(erlang, error, [?TERM(undefined)]). %%% ------------------------------------------------------------------------ @@ -272,11 +275,11 @@ vendor_id(error) -> f_vendor_name(Spec) -> {?function, vendor_name, 0, - [{?clause, [], [], [vendor_name(orddict:find(vendor, Spec))]}]}. + [{?clause, [], [], [b_vendor_name(orddict:find(vendor, Spec))]}]}. -vendor_name({ok, {_, Name}}) -> +b_vendor_name({ok, {_, Name}}) -> ?Atom(Name); -vendor_name(error) -> +b_vendor_name(error) -> ?APPLY(erlang, error, [?TERM(undefined)]). %%% ------------------------------------------------------------------------ @@ -290,8 +293,9 @@ 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}) -> @@ -480,15 +484,15 @@ not_in(List, X) -> c_base_avp({AvpName, T}) -> {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], - [base_avp(AvpName, T)]}. + [b_base_avp(AvpName, T)]}. -base_avp(AvpName, "Enumerated") -> +b_base_avp(AvpName, "Enumerated") -> ?CALL(enumerated_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); -base_avp(AvpName, "Grouped") -> +b_base_avp(AvpName, "Grouped") -> ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); -base_avp(_, Type) -> +b_base_avp(_, Type) -> ?APPLY(diameter_types, ?A(Type), [?VAR('T'), ?VAR('Data')]). cs_imported_avp({Mod, Avps}, Enums, CustomNames) -> @@ -522,14 +526,15 @@ cs_custom_avp({Mod, Key, Avps}, Dict) -> Avps). c_custom_avp(Mod, Key, AvpName, Type) -> + {F,A} = custom(Key, AvpName, Type), {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], [], - [custom(Mod, Key, AvpName, Type)]}. + [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), ?VAR('Data')])]}. -custom(Mod, custom_types, AvpName, Type) -> - ?APPLY(?A(Mod), ?A(AvpName), [?VAR('T'), ?Atom(Type), ?VAR('Data')]); -custom(Mod, codecs, AvpName, Type) -> - ?APPLY(?A(Mod), ?A(Type), [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]). +custom(custom_types, AvpName, Type) -> + {AvpName, Type}; +custom(codecs, AvpName, Type) -> + {Type, AvpName}. %%% ------------------------------------------------------------------------ %%% # enumerated_avp/3 -- cgit v1.2.3 From a6268f2f49882e06c2b79f34e1d43e36f7d361c9 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 11:54:46 +0100 Subject: Make diameter_types usable with @codecs --- lib/diameter/src/base/diameter_types.erl | 61 +++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) (limited to 'lib/diameter') 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). + %% =========================================================================== %% =========================================================================== -- cgit v1.2.3 From 337102b0220929f1f0b65302021ddead68292db4 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 16:32:06 +0100 Subject: Add diameter_dict_util:format/1 for reconstructing a dictionary file --- .../src/compiler/diameter_dict_scanner.erl | 21 ++- lib/diameter/src/compiler/diameter_dict_util.erl | 175 ++++++++++++++++++++- 2 files changed, 186 insertions(+), 10 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_dict_scanner.erl b/lib/diameter/src/compiler/diameter_dict_scanner.erl index 74bf0cb06a..963b58fdfc 100644 --- a/lib/diameter/src/compiler/diameter_dict_scanner.erl +++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl @@ -26,6 +26,8 @@ -export([scan/1, format_error/1]). +-export([is_name/1]). + %% ----------------------------------------------------------- %% # scan/1 %% ----------------------------------------------------------- @@ -77,10 +79,19 @@ scan(S, {Lineno, Acc}) -> {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; @@ -161,12 +172,10 @@ split([$'|T]) -> case splitwith(fun(C) -> not lists:member(C, "'\r\n") end, T) of {[_|_] = A, [$'|Rest]} -> {{word, A}, Rest}; - {[_|_], _} -> %% not terminated on same line - "Unterminated atom"; - {[], []} -> %% last character - "Unterminated atom"; - {[], _} -> - "Empty atom" + {[], [$'|_]} -> + "Empty string"; + _ -> %% not terminated on same line + "Unterminated string" end; %% Line ending of various forms. diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl index 7c992316f9..51a3091883 100644 --- a/lib/diameter/src/compiler/diameter_dict_util.erl +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -26,7 +26,8 @@ -module(diameter_dict_util). -export([parse/2, - format_error/1]). + format_error/1, + format/1]). -include("diameter_vsn.hrl"). @@ -35,6 +36,7 @@ -define(A, list_to_atom). -define(L, atom_to_list). +-define(I, integer_to_list). %% =========================================================================== %% parse/2 @@ -44,7 +46,7 @@ -> {ok, orddict:orddict()} | {error, term()} when File :: {path, string()} - | string() + | iolist() | binary(), Opts :: list(). @@ -68,7 +70,7 @@ format_error({read, Reason}) -> format_error({scan, Reason}) -> diameter_dict_scanner:format_error(Reason); format_error({parse, {Line, _Mod, Reason}}) -> - lists:flatten(["Line ", integer_to_list(Line), ", ", Reason]); + lists:flatten(["Line ", ?I(Line), ", ", Reason]); format_error(T) -> {Fmt, As} = fmt(T), @@ -182,6 +184,171 @@ 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, {Id, Name}) -> + [?NL, section(vendor) | [[?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 -> + if [] == Body -> + []; + true -> + [[?NL, pairs(K, T)] || T <- Body] + end. + +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}) -> + f_avp(f_qual(Q), f_delim(A)); +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, Avp) -> + Len = length(lists:flatten([Q])), + [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), Avp]. + +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) -> @@ -201,7 +368,7 @@ do([F|A], E) -> read({path, Path}) -> file:read_file(Path); read(File) -> - {ok, File}. + {ok, iolist_to_binary([File])}. make_dict(Parse, Opts) -> make_orddict(pass4(pass3(pass2(pass1(reset(make_dict(Parse), -- cgit v1.2.3 From c69639c5bec4d0e41af43ea272a70dcf1fc04e64 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 17:09:22 +0100 Subject: Add format testcase to compiler suite Plus additional parsing tests. --- lib/diameter/test/diameter_compiler_SUITE.erl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index 7caea3258e..c4de6e4f85 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -31,6 +31,7 @@ %% testcases -export([base/1, + format/1, replace/1, replace/2]). -export([dict/0]). %% fake dictionary module @@ -49,6 +50,9 @@ [{scan, "@id 0", "@id \\&"}, + {scan, + "@name ", + "&'"}, {parse, "@id 0", "@id @id"}, @@ -151,6 +155,9 @@ {requested_avp_not_found, [{"@id", "@inherits diameter_gen_base_rfc3588 XXX &"}, {"CEA ::=", " &"}]}, + {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"}, @@ -176,11 +183,12 @@ "@avp_types", "& bad syntax"}, {ok, - [{"^ *Class .*", "@avp_types"}, + [{"@avp_types", "& 3XXX 666 Time M 'X X X' 667 Time -"}, + {"^ *Class .*", "@avp_types"}, {"^ *Failed-AVP ", "@avp_types &"}, {"@grouped", "&&"}, {"^ *Failed-AVP ::=", "@grouped &"}, - {"CEA ::=", " &"}, + {"CEA ::=", "<'Class'> &"}, {"@avp_types", "@inherits diameter_gen_base_rfc3588 Class\n&"}, {"@avp_types", "@custom_types mymod " "Product-Name Firmware-Revision\n" @@ -194,6 +202,7 @@ suite() -> all() -> [base, + format, replace]. %% Error handling testcases will make an erroneous dictionary out of @@ -220,6 +229,13 @@ base(_Config) -> "diameter_gen_base_rfc3588"]), {module, _} = code:load_abs(Base). +%% Ensure that parse o format is the identity map. +format(Config) -> + Bin = proplists:get_value(base, Config), + {ok, Dict} = diameter_dict_util:parse(Bin, []), + {ok, D} = diameter_dict_util:parse(diameter_dict_util:format(Dict), []), + {Dict, Dict} = {Dict, D}. + %% replace/1 replace(Config) -> -- cgit v1.2.3 From 23dbc347fe872f953d0f146ecb87ddc34ea608d7 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 22:34:03 +0100 Subject: Tweak diameter_make interface --- lib/diameter/src/compiler/diameter_make.erl | 76 ++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 17 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl index a9195ba2d1..16e30c1ffb 100644 --- a/lib/diameter/src/compiler/diameter_make.erl +++ b/lib/diameter/src/compiler/diameter_make.erl @@ -20,32 +20,42 @@ %% %% Module alternative to diameterc for dictionary compilation. %% -%% Eg. 1> diameter_make:file("mydict.dia"). +%% Eg. 1> diameter_make:codec("mydict.dia"). %% -%% $ erl -noshell \ +%% $ erl -noinput \ %% -boot start_clean \ -%% -s diameter_make file mydict.dia \ +%% -eval 'ok = diameter_make:codec("mydict.dia")' \ %% -s init stop %% -module(diameter_make). --export([file/1, - file/2, +-export([codec/1, + codec/2, dict/1, - dict/2]). + dict/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 file(string(), [opt()]) +-spec codec(Path, [opt()]) -> ok - | {error, string()}. + | {error, Reason} + when Path :: string(), + Reason :: string(). -file(File, Opts) -> +codec(File, Opts) -> case dict(File, Opts) of {ok, Dict} -> make(File, @@ -56,10 +66,13 @@ file(File, Opts) -> E end. -file(File) -> - file(File, []). +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, orddict:orddict()} @@ -76,15 +89,44 @@ dict(Path, Opts) -> dict(File) -> dict(File, []). +%% 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). + +%% reformat/1 +%% +%% Parse a dictionary file and return its formatted equivalent. + +-spec reformat(File) + -> {ok, iolist()} + | {error, Reason} + when File :: string(), + Reason :: string(). + +reformat(File) -> + case dict(File) of + {ok, Dict} -> + {ok, format(Dict)}; + {error, _} = No -> + No + end. + %% =========================================================================== make(_, _, _, []) -> ok; make(File, Opts, Dict, [Mode | Rest]) -> - try diameter_codegen:from_dict(File, Dict, Opts, Mode) of - ok -> - make(File, Opts, Dict, 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. -- cgit v1.2.3 From 22f9111ef12ed58aeb5a15ed55a983c9b19cddb8 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 17:53:15 +0100 Subject: Don't explicitly load inherited modules Just use include options to add to the code path and expect that dependent modules will either already have been loaded or will be loaded dynamically, thereby avoiding having a module being left as both current and old code when compiling concurrently. Not a problem for a human user interactively compiling one module at a time but the compiler test suite for one will compile concurrently. In any case, leaving behind old code is probably not what someone would expect while relying on the code path probably is. --- lib/diameter/src/compiler/diameter_dict_util.erl | 91 ++++++++---------------- lib/diameter/test/diameter_compiler_SUITE.erl | 18 +---- 2 files changed, 34 insertions(+), 75 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl index 51a3091883..201b153f0f 100644 --- a/lib/diameter/src/compiler/diameter_dict_util.erl +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -51,14 +51,14 @@ Opts :: list(). parse(File, Opts) -> - put({?MODULE, verbose}, lists:member(verbose, Opts)), + putr(verbose, lists:member(verbose, Opts)), try {ok, do_parse(File, Opts)} catch {Reason, ?MODULE, _Line} -> {error, Reason} after - erase({?MODULE, verbose}) + eraser(verbose) end. %% =========================================================================== @@ -174,12 +174,11 @@ fmt(enumerated_avp_not_defined) -> fmt(avp_not_defined) -> "AVP ~s referenced at line ~p neither defined nor inherited"; -fmt(beam_not_on_path) -> - "Module ~s not found"; - 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". @@ -234,8 +233,8 @@ io(K, Id) K == prefix -> [?NL, section(K), ?SP, tok(Id)]; -io(vendor, {Id, Name}) -> - [?NL, section(vendor) | [[?SP, tok(X)] || X <- [Id, Name]]]; +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]]; @@ -252,11 +251,7 @@ io(K, Body) K == codecs; K == enum; K == define -> - if [] == Body -> - []; - true -> - [[?NL, pairs(K, T)] || T <- Body] - end. + [[?NL, pairs(K, T)] || T <- Body]. pairs(K, {Id, Avps}) -> [?NL, section(K), ?SP, tok(Id), ?NL, [[?NL, body(K, A)] || A <- Avps]]. @@ -510,7 +505,7 @@ report(What, [F | A]) when is_function(F) -> report(What, apply(F, A)); report(What, Data) -> - report(get({?MODULE, verbose}), What, Data). + report(getr(verbose), What, Data). report(true, Tag, Data) -> io:format("##~n## ~p ~p~n", [Tag, Data]); @@ -1114,18 +1109,16 @@ import_key({Mod, Avps}, Key) -> %% name, Line points to the corresponding @inherit and each Avp is %% from Mod:dict(). Lineno is 0 if the import is implicit. -inherit(Dict, Options) -> - Path = [D || {include, D} <- Options] - ++ [".", code:lib_dir(diameter, ebin)], - foldl([fun find_avps/3, Path], [], find(inherits, Dict)). +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. -find_avps([Line, {_,_,M} | Names], Acc, Path) -> +inherit_avps([Line, {_,_,M} | Names], Acc) -> Mod = ?A(M), report(inherit_from, Mod), - Avps = avps_from_beam(find_beam(Mod, Path), Mod), %% could be empty - case find_avps(Names, Avps) of + case find_avps(Names, avps_from_module(Mod)) of {_, [{_, L, N} | _]} -> ?RETURN(requested_avp_not_found, [Mod, Line, N, L]); {Found, []} -> @@ -1138,9 +1131,9 @@ find_avps([], Avps) -> %% ... or specified AVPs. find_avps(Names, Avps) -> - foldl(fun fa/2, {[], Names}, Avps). + foldl(fun acc_avp/2, {[], Names}, Avps). -fa({Name, _Code, _Type, _Flags} = A, {Found, Not} = Acc) -> +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)}; @@ -1148,45 +1141,11 @@ fa({Name, _Code, _Type, _Flags} = A, {Found, Not} = Acc) -> Acc end. -%% find_beam/2 +%% avps_from_module/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 -> - ?RETURN(beam_not_on_path, [Mod]) - 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), +avps_from_module(Mod) -> orddict:fetch(avp_types, dict(Mod)). -load_module(false, Mod, Path) -> - R = filename:rootname(Path, code:objfile_extension()), - {module, Mod} = code:load_abs(R), - ok; -load_module({file, _}, _, _) -> - ok. - dict(Mod) -> try Mod:dict() of [?VERSION | Dict] -> @@ -1194,8 +1153,11 @@ dict(Mod) -> _ -> ?RETURN(recompile, [Mod]) catch - error:_ -> - ?RETURN(no_dict, [Mod]) + error: _ -> + ?RETURN(choose(false == code:is_loaded(Mod), + not_loaded, + no_dict), + [Mod]) end. %% =========================================================================== @@ -1260,6 +1222,15 @@ avp_is_defined(Name, Dict, Line) -> %% =========================================================================== +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. diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index c4de6e4f85..e2f4b3c931 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -30,8 +30,7 @@ end_per_suite/1]). %% testcases --export([base/1, - format/1, +-export([format/1, replace/1, replace/2]). -export([dict/0]). %% fake dictionary module @@ -167,7 +166,7 @@ {avp_not_defined, "CEA ::=", " &"}, - {beam_not_on_path, + {not_loaded, [{"@avp_types", "@inherits nomod XXX &"}, {"CEA ::=", " &"}]}, {recompile, @@ -201,8 +200,7 @@ suite() -> [{timetrap, {seconds, 5}}]. all() -> - [base, - format, + [format, replace]. %% Error handling testcases will make an erroneous dictionary out of @@ -219,16 +217,6 @@ end_per_suite(_Config) -> %% =========================================================================== %% testcases -%% base/1 - -%% This work around a minor deficiency in diameter_dict_util, that it -%% does deal well with compiling in parallel. Load the dictionary -%% ahead of time so that compilation doesn't. -base(_Config) -> - Base = filename:join([code:lib_dir(diameter, ebin), - "diameter_gen_base_rfc3588"]), - {module, _} = code:load_abs(Base). - %% Ensure that parse o format is the identity map. format(Config) -> Bin = proplists:get_value(base, Config), -- cgit v1.2.3 From f51fa7f2a67cfdcbf98edaa79db32c54e5ee4af0 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 4 Dec 2011 12:06:30 +0100 Subject: Add range checks on dictionary integers Check that values that should be Unsigned32 actually are. --- lib/diameter/src/compiler/diameter_dict_util.erl | 87 +++++++++++++++++++----- lib/diameter/test/diameter_compiler_SUITE.erl | 9 +++ 2 files changed, 78 insertions(+), 18 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl index 201b153f0f..09aab4eff4 100644 --- a/lib/diameter/src/compiler/diameter_dict_util.erl +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -37,6 +37,7 @@ -define(A, list_to_atom). -define(L, atom_to_list). -define(I, integer_to_list). +-define(F, io_lib:format). %% =========================================================================== %% parse/2 @@ -76,15 +77,37 @@ format_error(T) -> {Fmt, As} = fmt(T), lists:flatten(io_lib:format(Fmt, As)). -fmt({avp_code_already_defined, [Code, false, Name, Line, L]}) -> - {"AVP ~p (~s) at line ~p already defined at line ~p", - [Code, Name, Line, L]}; +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/~p (~s) at line ~p already defined at line ~p"; + "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"; @@ -580,7 +603,9 @@ pass1(Dict) -> foldl(fun(K,D) -> foldl([fun p1/3, K], D, find(K,D)) end, Dict, - [avp_types, %% must precede inherits, grouped, enum + [id, + vendor, + avp_types, %% must precede inherits, grouped, enum avp_vendor_id, custom_types, codecs, @@ -593,6 +618,14 @@ pass1(Dict) -> %% 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; @@ -636,7 +669,8 @@ no_messages_without_id(Dict) -> %% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()] %% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()] -explode({_, Line, AvpName}, Dict, {_, _, X}, K) -> +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}, @@ -649,11 +683,13 @@ explode({_, Line, AvpName}, Dict, {_, _, X}, K) -> %% {define, {Name, Key}} -> [Lineno, Value::integer(), enum|define] -explode2([{_, Line, Key}, {_, _, Value}], Dict, {_, _, X}, K) -> - store_new({key(K), {X, Key}}, +explode2([{_, Line, Key}, {_, _, Value} = T], Dict, {_, _, Name}, K) -> + true = is_uint32(T, [K, Name]), + + store_new({key(K), {Name, Key}}, [Line, Value, K], Dict, - [X, Key, K, Line], + [Name, Key, K, Line], key_already_defined). %% key/1 @@ -683,17 +719,18 @@ key(K) -> explode([{_, Line, Name} | Toks], Dict0, avp_types = K) -> %% Each AVP can be defined only once. - Dict1 = store_new({K, Name}, - [Line | Toks], - Dict0, - [Name, Line], - avp_name_already_defined), + Dict = store_new({K, Name}, + [Line | Toks], + Dict0, + [Name, Line], + avp_name_already_defined), - [{number, _, _Code}, {word, _, Type}, {word, _, _Flags}] = Toks, + [{number, _, _Code} = C, {word, _, Type}, {word, _, _Flags}] = Toks, true = avp_type_known(Type, Name, Line), + true = is_uint32(C, [K, Name]), - Dict1; + Dict; %% {grouped, Name} -> [Line, HeaderTok | AvpToks] %% {grouped, {Name, AvpName}} -> [Line, Qual, Delim] @@ -711,10 +748,12 @@ explode([{_, Line, Name}, Header | Avps], Dict0, grouped = K) -> [Name, Line], group_already_defined), - [{_,_, Code}, Vid] = Header, + [{_,_, 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); @@ -756,7 +795,11 @@ explode([{_, Line, MsgName} = M, Header | Avps], [MsgName, Line], message_name_already_defined), - [{_, _, Code}, Bits, ApplId] = Header, + [{_, _, 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 @@ -830,6 +873,14 @@ 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) -> diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index e2f4b3c931..a224a4f12e 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -58,6 +58,15 @@ {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 &"}, -- cgit v1.2.3 From c11f047bc88406c25c52ff57d1062906a0aeff07 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 4 Dec 2011 12:38:43 +0100 Subject: Fix interpretation of vendor id in @grouped A value is required to be the same as any specified with @avp_vendor_id but otherwise the two locations are equivalent. Both possibilities are allowed since @avp_vendor_id is required for AVPs of types other than Grouped (modulo it not really needing to exist at all: see commit 943266c9) and since the grammar parsed in @grouped (from RFC 3588) allows it. --- lib/diameter/src/compiler/diameter_codegen.erl | 21 ++++++++++++++------- lib/diameter/src/compiler/diameter_dict_util.erl | 5 +++++ lib/diameter/test/diameter_compiler_SUITE.erl | 9 ++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 3105310c5b..6763e06140 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -376,9 +376,7 @@ avp_name(Spec) -> Avps = get_value(avp_types, Spec), Imported = get_value(import_avps, Spec), Vid = orddict:find(vendor, Spec), - - Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, - get_value(avp_vendor_id, Spec)), + Vs = vendor_id_map(Spec), lists:map(fun(T) -> c_avp_name(T, Vs, Vid) end, Avps) ++ lists:flatmap(fun(T) -> c_imported_avp_name(T, Vs) end, Imported) @@ -393,7 +391,9 @@ c_avp_name({Name, Code, Type, Flags}, Vs, Vid) -> %% 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. +%% 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). @@ -407,6 +407,14 @@ c_avp_name_(T, Code, Vid) -> [], [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 %%% ------------------------------------------------------------------------ @@ -615,9 +623,7 @@ avp_header(Spec) -> Native = get_value(avp_types, Spec), Imported = get_value(import_avps, Spec), Vid = orddict:find(vendor, Spec), - - Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, - get_value(avp_vendor_id, Spec)), + Vs = vendor_id_map(Spec), lists:flatmap(fun(A) -> c_avp_header(A, Vs, Vid) end, Native ++ Imported). @@ -633,6 +639,7 @@ c_avp_header({Mod, Avps}, Vs, _Vid) -> %% 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)], diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl index 09aab4eff4..2207925e49 100644 --- a/lib/diameter/src/compiler/diameter_dict_util.erl +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -907,6 +907,11 @@ avp_type_known(Type, Name, Line) -> 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]); diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index a224a4f12e..cc4b1ddac5 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -136,6 +136,9 @@ [{"(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&"}, @@ -244,10 +247,10 @@ replace({E, RE, Repl}, Bin) -> replace({E, Mods}, Bin) -> B = iolist_to_binary(lists:foldl(fun re/2, Bin, Mods)), - case diameter_dict_util:parse(B, [{include, here()}]) of - {ok, Dict} when E == ok -> + case {E, diameter_dict_util:parse(B, [{include, here()}]), Mods} of + {ok, {ok, Dict}, _} -> Dict; - {error, {E,_} = T} -> + {_, {error, {E,_} = T}, _} -> S = diameter_dict_util:format_error(T), true = nochar($", S, E), true = nochar($', S, E), -- cgit v1.2.3 From 16502f6af64fcc9eafe01a118d7c8c1a29073a66 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 4 Dec 2011 12:09:36 +0100 Subject: Move type definitions into diameter.erl --- lib/diameter/src/base/diameter.erl | 237 ++++++++++++++++++++++------ lib/diameter/src/base/diameter_capx.erl | 9 +- lib/diameter/src/base/diameter_peer_fsm.erl | 4 +- lib/diameter/src/base/diameter_service.erl | 13 +- lib/diameter/src/base/diameter_session.erl | 10 +- lib/diameter/src/base/diameter_types.hrl | 139 ---------------- lib/diameter/src/modules.mk | 1 - 7 files changed, 210 insertions(+), 203 deletions(-) delete mode 100644 lib/diameter/src/base/diameter_types.hrl (limited to 'lib/diameter') diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 2f721421d8..ecd3d9542a 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -38,17 +38,46 @@ 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("diameter_internal.hrl"). --include("diameter_types.hrl"). -%%% -------------------------------------------------------------------------- -%%% start/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% start/0 +%% --------------------------------------------------------------------------- -spec start() -> ok @@ -57,9 +86,9 @@ start() -> application:start(?APPLICATION). -%%% -------------------------------------------------------------------------- -%%% stop/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% stop/0 +%% --------------------------------------------------------------------------- -spec stop() -> ok @@ -68,9 +97,9 @@ start() -> stop() -> application:stop(?APPLICATION). -%%% -------------------------------------------------------------------------- -%%% start_service/2 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% start_service/2 +%% --------------------------------------------------------------------------- -spec start_service(service_name(), [service_opt()]) -> ok @@ -80,9 +109,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 +120,9 @@ start_service(SvcName, Opts) stop_service(SvcName) -> diameter_config:stop_service(SvcName). -%%% -------------------------------------------------------------------------- -%%% services/0 -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% services/0 +%% --------------------------------------------------------------------------- -spec services() -> [service_name()]. @@ -101,9 +130,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 +140,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 +152,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 +162,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 +172,9 @@ remove_transport(SvcName, Pred) -> subscribe(SvcName) -> diameter_service:subscribe(SvcName). -%%% -------------------------------------------------------------------------- -%%% # unsubscribe(SvcName) -%%% -------------------------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% unsubscribe/1 +%% --------------------------------------------------------------------------- -spec unsubscribe(service_name()) -> true. @@ -156,9 +182,9 @@ subscribe(SvcName) -> unsubscribe(SvcName) -> diameter_service:unsubscribe(SvcName). -%%% ---------------------------------------------------------- -%%% # session_id/1 -%%% ---------------------------------------------------------- +%% --------------------------------------------------------------------------- +%% session_id/1 +%% --------------------------------------------------------------------------- -spec session_id('DiameterIdentity'()) -> 'OctetString'(). @@ -166,9 +192,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 +202,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 +214,124 @@ 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()). + +%% 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_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_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 8b9314ad1c..b36160148b 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()}). 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() -> %% 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.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/modules.mk b/lib/diameter/src/modules.mk index b86a32a4f7..d3125a7b57 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -78,7 +78,6 @@ 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_vsn.hrl -- cgit v1.2.3 From 9ff1e2673ce71cc6eb67a1a630ad5a2e485cbea8 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 5 Dec 2011 15:06:43 +0100 Subject: Check compiler dependencies in app suite After having added a less than stellar dependency that broke the build. Parallel make could succeed by chance but not sequential. This ensures the error doesn't get past test. --- lib/diameter/test/diameter_app_SUITE.erl | 46 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 88bd542eb8..7da145fb3b 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_make]). + +-define(HELP_MODULES, [diameter_callback, + diameter_dbg, + diameter_info]). + %% =========================================================================== suite() -> @@ -93,15 +106,8 @@ vsn(Config) -> modules(Config) -> Mods = fetch(modules, fetch(app, Config)), Installed = code_mods(), - Help = [diameter_callback, - diameter_codegen, - diameter_dbg, - diameter_dict_parser, - diameter_dict_scanner, - diameter_dict_util, - diameter_exprecs, - diameter_info, - diameter_make], + Help = lists:sort(?HELP_MODULES ++ ?COMPILER_MODULES), + {[], Help} = {Mods -- Installed, lists:sort(Installed -- Mods)}. code_mods() -> @@ -168,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), @@ -184,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)). -- cgit v1.2.3 From 156a2a55eb6fb8a0dbc4276d7cb232b59648de8f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 17:08:43 +0100 Subject: Fix base_rfc3588.dia typo --- lib/diameter/src/dict/base_rfc3588.dia | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia index f7a0b717cd..4f287845ae 100644 --- a/lib/diameter/src/dict/base_rfc3588.dia +++ b/lib/diameter/src/dict/base_rfc3588.dia @@ -365,7 +365,7 @@ ;; 7.1.4. Transient Failures DIAMETER_AUTHENTICATION_REJECTED 4001 DIAMETER_OUT_OF_SPACE 4002 - ELECTION_LOST 4003 + DIAMETER_ELECTION_LOST 4003 ;; 7.1.5. Permanent Failures DIAMETER_AVP_UNSUPPORTED 5001 -- cgit v1.2.3 From d01981decae7fe7190825858e8a26959257876ae Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 17:24:01 +0100 Subject: Make typo fix backwards compatible Also, add better definitions for Termination-Cause so as to avoid grotesquely long macro names in the generated hrl, and for consistency with other enums. That said, the names are still too long to be very practical. --- lib/diameter/src/dict/base_rfc3588.dia | 81 +++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 17 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia index 4f287845ae..ac80f19e75 100644 --- a/lib/diameter/src/dict/base_rfc3588.dia +++ b/lib/diameter/src/dict/base_rfc3588.dia @@ -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 DIAMETER_ELECTION_LOST 4003 - -;; 7.1.5. Permanent Failures DIAMETER_AVP_UNSUPPORTED 5001 DIAMETER_UNKNOWN_SESSION_ID 5002 DIAMETER_AUTHORIZATION_REJECTED 5003 @@ -412,3 +447,15 @@ ;; E2E-Sequence ::= 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 -- cgit v1.2.3 From 3bd6fcfd8ae399a903e50a9510ee279bde154039 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 3 Dec 2011 17:31:33 +0100 Subject: Improve base_rfc3588.dia formatting --- lib/diameter/src/dict/base_rfc3588.dia | 98 +++++++++++++++++----------------- 1 file changed, 49 insertions(+), 49 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia index ac80f19e75..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 - LOGOUT 1 - SERVICE_NOT_PROVIDED 2 - BAD_ANSWER 3 - ADMINISTRATIVE 4 - LINK_BROKEN 5 - AUTH_EXPIRED 6 - USER_MOVED 7 - 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 @@ -344,47 +344,47 @@ @define Result-Code ;; 7.1.1. Informational - MULTI_ROUND_AUTH 1001 + MULTI_ROUND_AUTH 1001 ;; 7.1.2. Success - SUCCESS 2001 - LIMITED_SUCCESS 2002 + 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 + 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 + 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 + 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 -- cgit v1.2.3 From f2a4059d06f8b76d2c1da14197f170deebd64f45 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 30 Nov 2011 12:31:26 +0100 Subject: Update documentation --- lib/diameter/doc/src/diameter_dict.xml | 147 ++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 57 deletions(-) (limited to 'lib/diameter') diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 01616f519d..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. diameter_dict -Dictionary inteface of the diameter application. +Dictionary interface of the diameter application.

@@ -74,7 +74,7 @@ corresponding to applications defined in section 2.4 of RFC 3588: application with application identifier 0, diameter_gen_accounting for the Diameter Base Accounting application with application identifier 3 and -diameter_gen_relaythe Relay application with application +diameter_gen_relay 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. @@ -94,18 +94,16 @@ defined.

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.

+Whitespace separates individual tokens but is otherwise insignificant.

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.

@@ -115,7 +113,8 @@ The order in which sections are specified is unimportant.

Defines the integer Number as the Diameter Application Id of the application in question. -Required if the dictionary defines @messages. +Can occur at most once and is required if the dictionary defines +@messages. The section has empty content.

@@ -136,16 +135,13 @@ Example:

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.

+Can occur at most once and defaults to the name of the dictionary file +minus any extension if unspecified. +The section has empty content.

-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.

+Note that a dictionary module should have a unique name so as not collide +with existing modules in the system.

Example:

@@ -159,22 +155,22 @@ Example:

@prefix Name

-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.

+Defines Name as the prefix to be added to record and constant names +(followed by a '_' character) in the generated dictionary +module and hrl. +Can occur at most once. +The section has empty content.

-A prefix is optional but can -be used to disambiguate record and constant names -resulting from similarly named messages and AVPs in different -Diameter applications.

+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.

Example:

-@prefix etsi_e2_ +@prefix etsi_e2
@@ -182,10 +178,12 @@ Example:

@vendor Number Name

-Defines the integer Number as the the default Vendor-ID of AVPs 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.

@@ -200,10 +198,9 @@ Example:

@avp_vendor_id Number

-Defines the integer Number as the Vendor-ID of the AVPs listed in the +Defines the integer Number as the Vendor-Id of the AVPs listed in the section content, overriding the @vendor default. -The section content consists of AVP names. -Can occur zero or more times (with different values of Number).

+The section content consists of AVP names.

Example:

@@ -221,13 +218,27 @@ Region-Set @inherits Mod

-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.

+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.

+ +

+Note that an inherited AVP that sets the V flag takes its Vendor-Id +from either @avp_vendor_id in the inheriting dictionary or +@vendor in the inherited dictionary. +In particular, @avp_vendor_id in the inherited dictionary is +ignored. +Inheriting from a dictionary that specifies the required @vendor +is equivalent to using @avp_vendor_id with a copy of the +dictionary's definitions but the former makes for easier reuse.

-Can occur 0 or more times (with different values of Mod) but all -dictionaries should typically inherit RFC3588 AVPs from +All dictionaries should typically inherit RFC3588 AVPs from diameter_gen_base_rfc3588.

@@ -248,13 +259,11 @@ The section consists of definitions of the form

Name Code Type Flags

-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 DATA TYPES below or a -type as defined by a @custom_types section.

+where Code is the integer AVP code, Type identifies an AVP Data Format +as defined in DATA TYPES 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 '-' (minus) character if +none are to be set.

Example:

@@ -262,8 +271,8 @@ Example:

@avp_types -Location-Information 350 Grouped VM -Requested-Information 353 Enumerated V +Location-Information 350 Grouped MV +Requested-Information 353 Enumerated V

@@ -276,21 +285,36 @@ to 0 as mandated by the current draft standard.

@custom_types Mod

-Defines AVPs for which module Mod provides encode/decode. +Specifies AVPs for which module Mod provides encode/decode functions. The section contents consists of AVP names. -For each AVP Name in the content, Mod:Name(encode|decode, Type, Data) -is expected to provide encode/decode for values of the AVP, where Type -is the type of the AVP as declared in the @avp_types section -of the dictionary and Data is the value to encoded/decoded.

+For each such name, Mod:Name(encode|decode, Type, Data) 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 +@avp_types section of the dictionary and Data is the value to +encode/decode.

+ +

+Example:

+ + +@custom_types rfc4005_avps + +Framed-IP-Address + +
+@codecs Mod +

-Can occur 0 or more times (with different values of Mod).

+Like @custom_types but requires the specified module to export +Mod:Type(encode|decode, Name, Data) rather than +Mod:Name(encode|decode, Type, Data).

Example:

-@custom_types rfc4005_avps +@codecs rfc4005_avps Framed-IP-Address @@ -360,6 +384,10 @@ SIP-Deregistration-Reason ::= < AVP Header: 383 > [ SIP-Reason-Info ] * [ AVP ] + +

+Specifying a Vendor-Id in the definition of a grouped AVP is +equivalent to specifying it with @avp_vendor_id.

@enum Name @@ -371,11 +399,9 @@ Integer values can be prefixed with 0x to be interpreted as hexidecimal.

-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 @messages or -@grouped section.

+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.

Example:

@@ -390,11 +416,18 @@ REMOVE_SIP_SERVER 3
+@end + +

+Causes parsing of the dictionary to terminate: +any remaining content is ignored.

+
+

Comments can be included in a dictionary file using semicolon: -text from a semicolon to end of line is ignored.

+characters from a semicolon to end of line are ignored.

-- cgit v1.2.3