1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2010-2017. 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-module(diameter_codec_test).
22
23-export([base/0,
24         gen/1,
25         lib/0]).
26
27%%
28%% Test encode/decode of dictionary-related modules.
29%%
30
31-include("diameter.hrl").
32
33-define(RFC3588, diameter_gen_base_rfc3588).
34-define(RFC6733, diameter_gen_base_rfc6733).
35-define(BOOL, [true, false]).
36
37-define(A, list_to_atom).
38-define(S, atom_to_list).
39
40%% ===========================================================================
41%% Interface.
42
43base() ->
44    [] = run([[fun base/1, T] || T <- [zero, decode]]).
45
46gen(Mod) ->
47    Fs = [{Mod, F, []} || Mod /= diameter_gen_doic_rfc7683,
48                          F <- [name, id, vendor_id, vendor_name]],
49    [] = run(Fs ++ [[fun gen/2, Mod, T] || T <- [messages,
50                                                 command_codes,
51                                                 avp_types,
52                                                 grouped,
53                                                 enum,
54                                                 import_avps,
55                                                 import_groups,
56                                                 import_enums]]).
57
58lib() ->
59    Vs = {_,_,_} = values('Address'),
60    [] = run([[fun lib/2, N, Vs] || N <- [{1, true}, {3, false}]]).
61
62%% ===========================================================================
63%% Internal functions.
64
65lib({N,B}, {_,_,_} = T) ->
66    [] = run([[fun lib/2, A, B] || A <- element(N,T), is_tuple(A)]);
67
68lib(IP, B) ->
69    [] = run([[fun lib/3, IP, B, A] || A <- [IP, ntoa(tuple_to_list(IP))]]).
70
71lib(IP, B, A) ->
72    try diameter_lib:ipaddr(A) of
73        IP when B ->
74            ok
75    catch
76        error:_ when not B ->
77            ok
78    end.
79
80ntoa([_,_,_,_] = A) ->
81    [$.|S] = lists:append(["." ++ integer_to_list(N) || N <- A]),
82    S;
83ntoa([_,_,_,_,_,_,_,_] = A) ->
84    [$:|S] = lists:flatten([":" ++ io_lib:format("~.16B", [N]) || N <- A]),
85    S.
86
87%% ------------------------------------------------------------------------
88%% base/1
89%%
90%% Test of diameter_types.
91%% ------------------------------------------------------------------------
92
93base(T) ->
94    [] = run([[fun base/2, T, F] || F <- types()]).
95
96%% Ensure that 'zero' values encode only zeros.
97base(zero = T, F) ->
98    B = diameter_types:F(encode, T, opts()),
99    B = z(B);
100
101%% Ensure that we can decode what we encode and vice-versa, and that
102%% we can't decode invalid values.
103base(decode, F) ->
104    {Ts, Fs, Is} = values(F),
105    [] = run([[fun base_decode/3, F, true, V]  || V <- Ts]),
106    [] = run([[fun base_decode/3, F, false, V] || V <- Fs]),
107    [] = run([[fun base_invalid/2, F, V]       || V <- Is]).
108
109base_decode(F, Eq, Value) ->
110    d(fun(X,V) -> diameter_types:F(X, V, opts()) end, Eq, Value).
111
112base_invalid(F, Value) ->
113    try
114        base_decode(F, false, Value),
115        exit(nok)
116    catch
117        error: _ ->
118            ok
119    end.
120
121types() ->
122    [F || {F,2} <- diameter_types:module_info(exports)].
123
124%% ------------------------------------------------------------------------
125%% gen/2
126%%
127%% Test of generated encode/decode module.
128%% ------------------------------------------------------------------------
129
130gen(M, T) ->
131    [] = run(lists:map(fun(X) -> [fun gen/3, M, T, X] end,
132                       fetch(T, dict(M)))).
133
134fetch(T, Spec) ->
135    case orddict:find(T, Spec) of
136        {ok, L} ->
137            L;
138        error ->
139            []
140    end.
141
142gen(M, messages = T, {Name, Code, Flags, ApplId, Avps})
143  when is_list(Name) ->
144    gen(M, T, {?A(Name), Code, Flags, ApplId, Avps});
145
146gen(M, messages, {Name, Code, Flags, _, _}) ->
147    Rname = M:msg2rec(Name),
148    Name = M:rec2msg(Rname),
149    {Code, F, _} = M:msg_header(Name),
150    0 = F band 2#00001111,
151    Name = case M:msg_name(Code, lists:member('REQ', Flags)) of
152               N when Name /= 'answer-message' ->
153                   N;
154               '' when Name == 'answer-message', (M == ?RFC3588
155                                                  orelse M == ?RFC6733) ->
156                   Name
157           end,
158    [] = arity(M, Name, Rname);
159
160gen(M, command_codes, {Code, Req, Ans}) ->
161    Msgs = orddict:fetch(messages, dict(M)),
162    {_, Code, _, _, _} = lists:keyfind(Req, 1, Msgs),
163    {_, Code, _, _, _} = lists:keyfind(Ans, 1, Msgs);
164
165gen(M, avp_types = T, {Name, Code, Type, Flags})
166  when is_list(Name) ->
167    gen(M, T, {?A(Name), Code, ?A(Type), Flags});
168
169gen(M, avp_types, {Name, Code, Type, _Flags}) ->
170    {Code, Flags, VendorId} = M:avp_header(Name),
171    0 = Flags band 2#00011111,
172    V = undefined /= VendorId,
173    V = 0 /= Flags band 2#10000000,
174    {Name, Type} = M:avp_name(Code, VendorId),
175    B = M:empty_value(Name, #{module => M}),
176    B = z(B),
177    [] = avp_decode(M, Type, Name);
178
179gen(M, grouped = T, {Name, Code, Vid, Avps})
180  when is_list(Name) ->
181    gen(M, T, {?A(Name), Code, Vid, Avps});
182
183gen(M, grouped, {Name, _, _, _}) ->
184    Rname = M:name2rec(Name),
185    [] = arity(M, Name, Rname);
186
187gen(M, enum = T, {Name, ED})
188  when is_list(Name) ->
189    gen(M, T, {?A(Name), lists:map(fun({E,D}) -> {?A(E), D} end, ED)});
190
191gen(M, enum, {Name, ED}) ->
192    [] = run([[fun enum/3, M, Name, T] || T <- ED]);
193
194gen(M, Tag, {_Mod, L}) ->
195    T = retag(Tag),
196    [] = run([[fun gen/3, M, T, I] || I <- L]).
197
198%% avp_decode/3
199
200avp_decode(Mod, Type, Name) ->
201    {Ts, Fs, _} = values(Type, Name, Mod),
202    [] = run([[fun avp_decode/5, Mod, Name, Type, true, V]
203              || V <- v(Ts)]),
204    [] = run([[fun avp_decode/5, Mod, Name, Type, false, V]
205              || V <- v(Fs)]).
206
207avp_decode(Mod, Name, Type, Eq, Value) ->
208    d(fun(X,V) -> avp(Mod, X, V, Name, Type) end, Eq, Value).
209
210avp(Mod, decode = X, V, Name, 'Grouped') ->
211    {Rec, _} = Mod:avp(X, V, Name, opts(Mod)),
212    Rec;
213avp(Mod, decode = X, V, Name, _) ->
214    Mod:avp(X, V, Name, opts(Mod));
215avp(Mod, encode = X, V, Name, _) ->
216    iolist_to_binary(Mod:avp(X, V, Name, opts(Mod))).
217
218opts(Mod) ->
219    (opts())#{module => Mod,
220              app_dictionary => Mod}.
221
222opts() ->
223    #{decode_format => record,
224      string_decode => true,
225      strict_mbit => true,
226      rfc => 6733,
227      failed_avp => false}.
228
229%% v/1
230
231%% List of values ...
232v(Vs)
233  when is_list(Vs) ->
234    Vs;
235
236%% .. or enumeration for grouped avps. This could be quite large
237%% (millions of values) but since the avps are also tested
238%% individually don't bother trying everything. Instead, choose a
239%% reasonable number of values at random.
240v(E) ->
241    v(2000, E(0), E).
242
243v(Max, Ord, E)
244  when Ord =< Max ->
245    diameter_enum:to_list(E);
246v(Max, Ord, E) ->
247    v(Max, Ord, E, []).
248
249v(0, _, _, Acc) ->
250    Acc;
251v(N, Ord, E, Acc) ->
252    v(N-1, Ord, E, [E(rand:uniform(Ord)) | Acc]).
253
254%% arity/3
255
256arity(M, Name, Rname) ->
257    Rec = M:'#new-'(Rname),
258    [] = run([[fun arity/4, M, Name, F, Rec]
259              || F <- M:'#info-'(Rname, fields)]).
260
261arity(M, Name, AvpName, Rec) ->
262    Def = M:'#get-'(AvpName, Rec),
263    Def = case M:avp_arity(Name, AvpName) of
264              1 ->
265                  undefined;
266              A when 0 /= A ->
267                  []
268          end.
269
270%% enum/3
271
272enum(M, Name, {_,E}) ->
273    B = <<E:32>>,
274    B = M:avp(encode, E, Name, opts(M)),
275    E = M:avp(decode, B, Name, opts(M)).
276
277retag(import_avps)   -> avp_types;
278retag(import_groups) -> grouped;
279retag(import_enums)  -> enum;
280
281retag(avp_types) -> import_avps;
282retag(enum)     -> import_enums.
283
284%% ===========================================================================
285
286d(F, Eq, V) ->
287    B = F(encode, V),
288    D = F(decode, B),
289    V = if Eq ->    %% test for value equality ...
290                D;
291           true ->  %% ... or that encode/decode is idempotent
292                D = F(decode, F(encode, D)),
293                V
294        end.
295
296z(B) ->
297    Sz = size(B),
298    <<0:Sz/unit:8>>.
299
300%% values/1
301%%
302%% Return a list of base type values. Can also be wrapped in a tuple
303%% with 'false' to indicate that encode followed by decode may not be
304%% the identity map. (Although that this composition is idempotent is
305%% tested.)
306
307values('OctetString' = T) ->
308    {["", atom_to_list(T)],
309     [],
310     [-1, 256]};
311
312values('Integer32') ->
313    Mx = (1 bsl 31) - 1,
314    Mn = -1*Mx,
315    {[Mn, 0, random(Mn,Mx), Mx],
316     [],
317     [Mn - 1, Mx + 1]};
318
319values('Integer64') ->
320    Mx = (1 bsl 63) - 1,
321    Mn = -1*Mx,
322    {[Mn, 0, random(Mn,Mx), Mx],
323     [],
324     [Mn - 1, Mx + 1]};
325
326values('Unsigned32') ->
327    M = (1 bsl 32) - 1,
328    {[0, random(M), M],
329     [],
330     [-1, M + 1]};
331
332values('Unsigned64') ->
333    M = (1 bsl 64) - 1,
334    {[0, random(M), M],
335     [],
336     [-1, M + 1]};
337
338values('Float32') ->
339    E = (1 bsl  8) - 2,
340    F = (1 bsl 23) - 1,
341    <<Mx:32/float>> = <<0:1, E:8, F:23>>,
342    <<Mn:32/float>> = <<1:1, E:8, F:23>>,
343    {[0.0, infinity, '-infinity', Mx, Mn],
344     [],
345     [0]};
346
347values('Float64') ->
348    E = (1 bsl 11) - 2,
349    F = (1 bsl 52) - 1,
350    <<Mx:64/float>> = <<0:1, E:11, F:52>>,
351    <<Mn:64/float>> = <<1:1, E:11, F:52>>,
352    {[0.0, infinity, '-infinity', Mx, Mn],
353     [],
354     [0]};
355
356values('Address') ->
357    {[{255,0,random(16#FF),1}, {65535,0,0,random(16#FFFF),0,0,0,1}],
358     ["127.0.0.1", "FFFF:FF::1.2.3.4"],
359     [{256,0,0,1}, {65536,0,0,0,0,0,0,1}, "256.0.0.1", "10000::1"]};
360
361values('DiameterIdentity') ->
362    {["x", "diameter.com"],
363     [],
364     [""]};
365
366values('DiameterURI') ->
367    {[],
368     ["aaa" ++ S ++ "://diameter.se" ++ P ++ Tr ++ Pr
369      || S  <- ["", "s"],
370         P  <- ["", ":1234", ":0", ":65535"],
371         Tr <- ["" | [";transport=" ++ X
372                      || X <- ["tcp", "sctp", "udp"]]],
373         Pr <- ["" | [";protocol=" ++ X
374                      || X <- ["diameter","radius","tacacs+"]]],
375         Tr /= ";transport=udp"
376             orelse (Pr /= ";protocol=diameter" andalso Pr /= "")]
377     ++ ["aaa://" ++ lists:duplicate(255, $x)],
378     ["aaa://diameter.se:65536",
379      "aaa://diameter.se:-1",
380      "aaa://diameter.se;transport=udp;protocol=diameter",
381      "aaa://diameter.se;transport=udp",
382      "aaa://" ++ lists:duplicate(256, $x),
383      "aaa://:3868",
384      "aaax://diameter.se",
385      "aaa://diameter.se;transport=tcpx",
386      "aaa://diameter.se;transport=tcp;protocol=diameter "]};
387
388values(T)
389  when T == 'IPFilterRule';
390       T == 'QoSFilterRule' ->
391    {["deny in 0 from 127.0.0.1 to 10.0.0.1"],
392     [],
393     []};
394
395%% RFC 3629 defines the UTF-8 encoding of U+0000 through U+10FFFF with the
396%% exception of U+D800 through U+DFFF.
397values('UTF8String') ->
398    S = "ᚠᚢᚦᚨᚱᚲ",
399    B = unicode:characters_to_binary(S),
400    {[[],
401      S,
402      lists:seq(0,16#1FF),
403      [0,16#D7FF,16#E000,16#10FFFF],
404      [random(16#D7FF), random(16#E000,16#10FFFF)]],
405     [B, [B, S, hd(S)], [S, B]],
406     [[-1],
407      [16#D800],
408      [16#DFFF],
409      [16#110000]]};
410
411values('Time') ->
412    {[{{1968,1,20},{3,14,8}},    %% 19000101T000000 + 1 bsl 31
413      {date(), time()},
414      {{2036,2,7},{6,28,15}},
415      {{2036,2,7},{6,28,16}},    %% 19000101T000000 + 2 bsl 31
416      {{2104,2,26},{9,42,23}}],
417     [],
418     [{{1968,1,20},{3,14,7}},
419      {{2104,2,26},{9,42,24}}]}. %% 19000101T000000 + 3 bsl 31
420
421%% values/3
422%%
423%% Return list or enumerations of values for a given AVP. Can be
424%% wrapped as for values/1.
425
426values('Enumerated', Name, Mod) ->
427    {_Name, Vals} = lists:keyfind(?S(Name), 1, types(enum, Mod)),
428    {lists:map(fun({_,N}) -> N end, Vals),
429     [],
430     []};
431
432values('Grouped', Name, Mod) ->
433    Rname = Mod:name2rec(Name),
434    Rec = Mod:'#new-'(Rname),
435    Avps = Mod:'#info-'(Rname, fields),
436    Enum = diameter_enum:combine(lists:map(fun({Vs,_,_}) -> to_enum(Vs) end,
437                                           [values(F, Mod) || F <- Avps])),
438    {[],
439     diameter_enum:append(group(Mod, Name, Rec, Avps, Enum)),
440     []};
441
442values(_, 'Framed-IP-Address', _) ->
443    {[{127,0,0,1}],
444     [],
445     []};
446
447values(Type, _, _) ->
448    values(Type).
449
450to_enum(Vs)
451  when is_list(Vs) ->
452    diameter_enum:new(Vs);
453to_enum(E) ->
454    E.
455
456%% values/2
457
458values('AVP', _) ->
459    {[#diameter_avp{code = 0, data = <<0>>}],
460     [],
461     []};
462
463values(Name, Mod) ->
464    Avps = types(avp_types, Mod),
465    {_Name, _Code, Type, _Flags} = lists:keyfind(?S(Name), 1, Avps),
466    values(?A(Type), Name, Mod).
467
468%% group/5
469%%
470%% Pack four variants of group values: tagged list containing all
471%% values, the corresponding record, a minimal tagged list and the
472%% coresponding record.
473
474group(Mod, Name, Rec, Avps, Enum) ->
475    lists:map(fun(B) -> group(Mod, Name, Rec, Avps, Enum, B) end,
476              [{A,R} || A <- ?BOOL, R <- ?BOOL]).
477
478group(Mod, Name, Rec, Avps, Enum, B) ->
479    diameter_enum:map(fun(Vs) -> g(Mod, Name, Rec, Avps, Vs, B) end, Enum).
480
481g(Mod, Name, Rec, Avps, Values, {All, AsRec}) ->
482    {Tagged, []}
483        = lists:foldl(fun(N, {A, [V|Vs]}) ->
484                              {pack(All, Mod:avp_arity(Name, N), N, V, A), Vs}
485                      end,
486                      {[], Values},
487                      Avps),
488    g(AsRec, Mod, Tagged, Rec).
489
490g(true, Mod, Vals, Rec) ->
491    Mod:'#set-'(Vals, Rec);
492g(false, _, Vals, _) ->
493    Vals.
494
495pack(true, Arity, Avp, Value, Acc) ->
496    [all(Arity, Avp, Value) | Acc];
497pack(false, Arity, Avp, Value, Acc) ->
498    min(Arity, Avp, Value, Acc).
499
500all(1, Avp, V) ->
501    {Avp, V};
502all({0,'*'}, Avp, V) ->
503    a(1, Avp, V);
504all({N,'*'}, Avp, V) ->
505    a(N, Avp, V);
506all({_,N}, Avp, V) ->
507    a(N, Avp, V).
508
509a(N, Avp, V)
510  when N /= 0 ->
511    {Avp, lists:duplicate(N,V)}.
512
513min(1, Avp, V, Acc) ->
514    [{Avp, V} | Acc];
515min({0,_}, _, _, Acc) ->
516    Acc;
517min({N,_}, Avp, V, Acc) ->
518    [{Avp, lists:duplicate(N,V)} | Acc].
519
520%% types/2
521
522types(T, Mod) ->
523    types(T, retag(T), Mod).
524
525types(T, IT, Mod) ->
526    Dict = dict(Mod),
527    fetch(T, Dict) ++ lists:flatmap(fun({_,As}) -> As end, fetch(IT, Dict)).
528
529%% random/[12]
530
531random(M) ->
532    random(0,M).
533
534random(Mn,Mx) ->
535    Mn + rand:uniform(Mx - Mn + 1) - 1.
536
537%% run/1
538%%
539%% Unravel nested badmatches resulting from [] matches on calls to
540%% run/1 to make for more readable failures.
541
542run(L) ->
543    lists:flatmap(fun flatten/1, diameter_util:run(L)).
544
545flatten({_, {{badmatch, [{_, {{badmatch, _}, _}} | _] = L}, _}}) ->
546    L;
547flatten(T) ->
548    [T].
549
550%% dict/1
551
552dict(Mod) ->
553    tl(Mod:dict()).
554