1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2010-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Module alternative to diameterc for dictionary compilation. 23%% 24%% Eg. 1> diameter_make:codec("mydict.dia"). 25%% 26%% $ erl -noinput \ 27%% -boot start_clean \ 28%% -eval 'ok = diameter_make:codec("mydict.dia")' \ 29%% -s init stop 30%% 31 32-module(diameter_make). 33 34-export([codec/2, 35 codec/1, 36 format/1, 37 flatten/1, 38 format_error/1]). 39 40-export_type([opt/0]). 41 42-include("diameter_vsn.hrl"). 43 44%% Options passed to codec/2. 45-type opt() :: {include|outdir|name|prefix|inherits, string()} 46 | return 47 | verbose 48 | parse %% internal parsed form 49 | forms %% abstract format for compile:forms/1,2 50 | erl 51 | hrl. 52 53%% Internal parsed format with a version tag. 54-type parsed() :: list(). 55 56%% Literal dictionary or path. A NL of CR identifies the former. 57-type dict() :: iolist() 58 | binary() 59 | parsed(). %% as returned by codec/2 60 61%% Name of a literal dictionary if otherwise unspecified. 62-define(DEFAULT_DICT_FILE, "dictionary.dia"). 63 64%% =========================================================================== 65 66%% codec/1-2 67%% 68%% Parse a dictionary file and generate a codec module. Input 69%% dictionary can be either a path or the dictionary itself: the 70%% occurrence of \n or \r in the argument is used to distinguish the 71%% two. 72 73-spec codec(File, [opt()]) 74 -> ok 75 | {ok, list()} %% with option 'return', one element for each output 76 | {error, Reason} 77 when File :: dict() 78 | {path, file:name_all()}, 79 Reason :: string(). 80 81codec(File, Opts) -> 82 {Dict, Path} = identify(File), 83 case parse(Dict, Opts) of 84 {ok, ParseD} -> 85 make(Path, default(Opts), ParseD); 86 {error, _} = E -> 87 E 88 end. 89 90codec(File) -> 91 codec(File, []). 92 93%% format/1 94%% 95%% Turn an orddict returned by dict/1-2 back into a dictionary. 96 97-spec format(parsed()) 98 -> iolist(). 99 100format([?VERSION | Dict]) -> 101 diameter_dict_util:format(Dict). 102 103%% flatten/1 104%% 105%% Reconstitute a dictionary without @inherits. 106 107-spec flatten(parsed()) 108 -> parsed(). 109 110flatten([?VERSION = V | Dict]) -> 111 [V | lists:foldl(fun flatten/2, 112 Dict, 113 [avp_vendor_id, 114 custom_types, 115 codecs, 116 [avp_types, import_avps], 117 [grouped, import_groups], 118 [enum, import_enums]])]. 119 120%% format_error/1 121 122format_error(T) -> 123 diameter_dict_util:format_error(T). 124 125%% =========================================================================== 126 127%% flatten/2 128 129flatten([_,_] = Keys, Dict) -> 130 [Values, Imports] = [orddict:fetch(K, Dict) || K <- Keys], 131 Vs = lists:append([Values | [V || {_Mod, V} <- Imports]]), 132 lists:foldl(fun({K,V},D) -> orddict:store(K,V,D) end, 133 Dict, 134 lists:zip([inherits | Keys], [[], Vs, []])); 135 136%% Inherited avp's setting the 'V' flag get their value either from 137%% @avp_vendor_id in the inheriting dictionary or from @vendor in the 138%% *inherited* (not inheriting) dictionary: add the latter to 139%% @avp_vendor_id as required. 140flatten(avp_vendor_id = Key, Dict) -> 141 Def = orddict:find(vendor, Dict), 142 ModD = imports(Dict), 143 Vids = orddict:fetch(Key, Dict), 144 Avps = lists:append([As || {_,As} <- Vids]), 145 orddict:store(Key, 146 dict:fold(fun(M, As, A) -> vid(M, As -- Avps, Def, A) end, 147 Vids, 148 ModD), 149 Dict); 150 151%% Import @codecs and @custom_types from inherited dictionaries as 152%% required. 153flatten(Key, Dict) -> 154 ImportAvps = orddict:fetch(import_avps, Dict), 155 ImportItems = [{M, As} 156 || {Mod, Avps} <- ImportAvps, 157 [_|D] <- [Mod:dict()], 158 {M,As0} <- orddict:fetch(Key, D), 159 F <- [fun(A) -> lists:keymember(A, 1, Avps) end], 160 [_|_] = As <- [lists:filter(F, As0)]], 161 orddict:store(Key, 162 lists:foldl(fun merge/2, 163 orddict:fetch(Key, Dict), 164 ImportItems), 165 Dict). 166 167%% merge/2 168 169merge({Mod, _Avps} = T, Acc) -> 170 merge(lists:keyfind(Mod, 1, Acc), T, Acc). 171 172merge({Mod, Avps}, {Mod, As}, Acc) -> 173 lists:keyreplace(Mod, 1, Acc, {Mod, Avps ++ As}); 174merge(false, T, Acc) -> 175 [T | Acc]. 176 177%% imports/1 178%% 179%% Return a module() -> [AVP] dict of inherited AVP's setting the V flag. 180 181imports(Dict) -> 182 lists:foldl(fun imports/2, 183 dict:new(), 184 orddict:fetch(import_avps, Dict)). 185 186imports({Mod, Avps}, Dict) -> 187 dict:store(Mod, 188 [A || {A,_,_,Fs} <- Avps, lists:member($V, Fs)], 189 Dict). 190 191%% vid/4 192 193vid(_, [], _, Acc) -> 194 Acc; 195vid(Mod, Avps, Def, Acc) -> 196 v(Mod:vendor_id(), Avps, Def, Acc). 197 198v(Vid, _, {ok, {Vid, _}}, Acc) -> %% same id as inheriting dictionary's 199 Acc; 200v(Vid, Avps, _, Acc) -> 201 case lists:keyfind(Vid, 1, Acc) of 202 {Vid, As} -> 203 lists:keyreplace(Vid, 1, Acc, {Vid, As ++ Avps}); 204 false -> 205 [{Vid, Avps} | Acc] 206 end. 207 208%% =========================================================================== 209 210parse({dict, ParseD}, _) -> 211 {ok, ParseD}; 212parse(File, Opts) -> 213 diameter_dict_util:parse(File, Opts). 214 215default(Opts) -> 216 def(modes(Opts), Opts). 217 218def([], Opts) -> 219 [erl, hrl | Opts]; 220def(_, Opts) -> 221 Opts. 222 223modes(Opts) -> 224 lists:filter(fun is_mode/1, Opts). 225 226is_mode(T) -> 227 lists:member(T, [erl, hrl, parse, forms]). 228 229identify([Vsn | [T|_] = ParseD]) 230 when is_tuple(T) -> 231 ?VERSION == Vsn orelse erlang:error({version, {Vsn, ?VERSION}}), 232 {{dict, ParseD}, ?DEFAULT_DICT_FILE}; 233identify({path, File} = T) -> 234 {T, File}; 235identify(File) -> 236 case is_path([File]) of 237 true -> {{path, File}, File}; 238 false -> {File, ?DEFAULT_DICT_FILE} 239 end. 240 241%% Interpret anything containing \n or \r as a literal dictionary. 242 243is_path([<<C,B/binary>> | T]) -> 244 is_path([C, B | T]); 245 246is_path([[C|L] | T]) -> 247 is_path([C, L | T]); 248 249is_path([C|_]) 250 when $\n == C; 251 $\r == C -> 252 false; 253 254is_path([_|T]) -> 255 is_path(T); 256 257is_path([]) -> 258 true. 259 260make(File, Opts, Dict) -> 261 ok(lists:foldl(fun(M,A) -> [make(File, Opts, Dict, M) | A] end, 262 [], 263 modes(Opts))). 264 265ok([ok|_]) -> 266 ok; 267ok([_|_] = L) -> 268 {ok, lists:reverse(L)}. 269 270make(File, Opts, Dict, Mode) -> 271 try 272 diameter_codegen:from_dict(File, Dict, Opts, Mode) 273 catch 274 error: Reason: Stack -> 275 erlang:error({Reason, Mode, Stack}) 276 end. 277