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