1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2013-2020. 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%% Tests of events sent as a consequence of diameter:subscribe/1.
23%% Watchdog events are dealt with more extensively in the watchdog
24%% suite.
25%%
26
27-module(diameter_event_SUITE).
28
29-export([suite/0,
30         all/0,
31         init_per_suite/1,
32         end_per_suite/1,
33         init_per_testcase/2,
34         end_per_testcase/2]).
35
36%% testcases
37-export([start/1,
38         start_server/1,
39         up/1,
40         down/1,
41         cea_timeout/1,
42         stop/1]).
43
44-include("diameter.hrl").
45
46%% ===========================================================================
47
48-define(util, diameter_util).
49
50-define(ADDR, {127,0,0,1}).
51-define(REALM, "REALM").
52
53-define(SERVER, "SERVER.SERVER-REALM").
54-define(CLIENT, "CLIENT.CLIENT-REALM").
55
56-define(DICT_COMMON, ?DIAMETER_DICT_COMMON).
57-define(DICT_ACCT,   ?DIAMETER_DICT_ACCOUNTING).
58
59-define(SERVER_CAPX_TMO, 6000).
60
61%% Config for diameter:start_service/2.
62-define(SERVICE(Host, Dicts),
63        [{'Origin-Host', Host},
64         {'Origin-Realm', realm(Host)},
65         {'Host-IP-Address', [?ADDR]},
66         {'Vendor-Id', 12345},
67         {'Product-Name', "OTP/diameter"},
68         {'Acct-Application-Id', [D:id() || D <- Dicts]},
69         {decode_format, map}
70         | [{application, [{dictionary, D},
71                           {module, #diameter_callback{}}]}
72            || D <- Dicts]]).
73
74%% Diameter Result-Code's:
75-define(NO_COMMON_APP, 5010).
76
77%% ===========================================================================
78
79suite() ->
80    [{timetrap, {seconds, 60}}].
81
82all() ->
83    [start,
84     start_server,
85     up,
86     down,
87     cea_timeout,
88     stop].
89
90%% Not used, but a convenient place to enable trace.
91init_per_suite(Config) ->
92    Config.
93
94end_per_suite(_Config) ->
95    ok.
96
97init_per_testcase(Name, Config) ->
98    [{name, Name} | Config].
99
100end_per_testcase(_, _) ->
101    ok.
102
103%% ===========================================================================
104%% start/stop testcases
105
106start(_Config) ->
107    ok = diameter:start().
108
109start_server(Config) ->
110    diameter:subscribe(?SERVER),
111    ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER, [?DICT_COMMON])),
112    LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx_cb/2},
113                                       {capx_timeout, ?SERVER_CAPX_TMO}]),
114    [PortNr] = ?util:lport(tcp, LRef),
115    ?util:write_priv(Config, portnr, PortNr),
116    start = event(?SERVER).
117
118%% Connect with matching capabilities and expect the connection to
119%% come up.
120up(Config) ->
121    {Svc, Ref, T} = connect(Config, [{strict_mbit, false},
122                                     {connect_timer, 5000},
123                                     {watchdog_timer, 15000}]),
124    start = event(Svc),
125    {{up, Ref, {TPid, Caps}, T, #diameter_packet{msg = M}}, _}
126        = {event(Svc), T},
127    ['CEA' | #{}] = M,  %% assert
128    {watchdog, Ref, _, {initial, okay}, _} = event(Svc),
129    %% Kill the transport process and see that the connection is
130    %% reestablished after a watchdog timeout, not after connect_timer
131    %% expiry.
132    exit(TPid, kill),
133    {{down, Ref, {TPid, Caps}, T}, _} = {event(Svc), T},
134    {watchdog, Ref, _, {okay, down}, _} = event(Svc),
135    {reconnect, Ref, _} = event(Svc, 10000, 20000).
136
137%% Connect with non-matching capabilities and expect CEA from the peer
138%% to indicate as much and then for the transport to be restarted
139%% (after connect_timer).
140down(Config) ->
141    {Svc, Ref, T} = connect(Config, [{capabilities, [{'Acct-Application-Id',
142                                                      [?DICT_ACCT:id()]}]},
143                                     {applications, [?DICT_ACCT]},
144                                     {connect_timer, 5000},
145                                     {watchdog_timer, 20000}]),
146    start = event(Svc),
147    {{closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{msg = M}}, T},
148     _}
149        = {event(Svc), T},
150    ['CEA' | #{}] = M,  %% assert
151    {reconnect, Ref, _} = event(Svc, 4000, 10000).
152
153%% Connect with matching capabilities but have the server delay its
154%% CEA and cause the client to timeout.
155cea_timeout(Config) ->
156    {Svc, Ref, T} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2},
157                                     {connect_timer, 2*?SERVER_CAPX_TMO}]),
158    start = event(Svc),
159    {{closed, Ref, {'CEA', timeout}, T}, _} = {event(Svc), T}.
160
161stop(_Config) ->
162    ok = diameter:stop().
163
164%% ----------------------------------------
165
166%% Keep the server from sending CEA until the client has timed out.
167capx_cb(_, #diameter_caps{origin_host = {_, "cea_timeout-" ++ _}}) ->
168    receive after ?SERVER_CAPX_TMO -> ok end;
169
170%% Or not.
171capx_cb(_, _Caps) ->
172    ok.
173
174%% ----------------------------------------
175
176%% Use the testcase name to construct Origin-Host of the client so
177%% that the server can match on it in capx_cb/2.
178connect(Config, Opts) ->
179    Pre = atom_to_list(proplists:get_value(name, Config)),
180    Name = Pre ++ uniq() ++ ?CLIENT,
181    diameter:subscribe(Name),
182    ok = start_service(Name, ?SERVICE(Name, [?DICT_COMMON, ?DICT_ACCT])),
183    {connect, _} = T = opts(Config, Opts),
184    {ok, Ref} = diameter:add_transport(Name, T),
185    {Name, Ref, T}.
186
187uniq() ->
188    "-" ++ diameter_util:unique_string().
189
190event(Name) ->
191    receive #diameter_event{service = Name, info = T} -> T end.
192
193event(Name, TL, TH) ->
194    T0 = diameter_lib:now(),
195    Event = event(Name),
196    DT = diameter_lib:micro_diff(T0) div 1000,
197    {true, true, DT, Event} = {TL < DT, DT < TH, DT, Event},
198    Event.
199
200start_service(Name, Opts) ->
201    diameter:start_service(Name, [{monitor, self()} | Opts]).
202
203opts(Config, Opts) ->
204    PortNr = ?util:read_priv(Config, portnr),
205
206    {connect, [{transport_module, diameter_tcp},
207               {transport_config, [{ip, ?ADDR}, {port, 0},
208                                   {raddr, ?ADDR}, {rport, PortNr}]}
209               | Opts]}.
210
211realm(Host) ->
212    tl(lists:dropwhile(fun(C) -> C /= $. end, Host)).
213