1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2013-2016. 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 transport_opt() length_errors.
23%%
24
25-module(diameter_length_SUITE).
26
27-export([suite/0,
28         all/0,
29         groups/0,
30         init_per_suite/1,
31         end_per_suite/1,
32         init_per_group/2,
33         end_per_group/2,
34         init_per_testcase/2,
35         end_per_testcase/2]).
36
37%% testcases
38-export([start/1,
39         send/1,
40         stop/1]).
41
42%% diameter callbacks
43-export([peer_up/3,
44         peer_down/3,
45         pick_peer/5,
46         prepare_request/4,
47         handle_answer/5,
48         handle_error/5,
49         handle_request/3]).
50
51-include("diameter.hrl").
52-include("diameter_gen_base_rfc3588.hrl").
53
54%% ===========================================================================
55
56-define(util, diameter_util).
57
58-define(CLIENT, "CLIENT").
59-define(SERVER, "SERVER").
60-define(REALM, "erlang.org").
61-define(HOST(Host, Realm), Host ++ [$.|Realm]).
62-define(DICT, diameter_gen_base_rfc3588).
63
64%% Config for diameter:start_service/2.
65-define(SERVICE(Name),
66        [{'Origin-Host', Name ++ "." ++ ?REALM},
67         {'Origin-Realm', ?REALM},
68         {'Host-IP-Address', [{127,0,0,1}]},
69         {'Vendor-Id', 12345},
70         {'Product-Name', "OTP/diameter"},
71         {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]},
72         {application, [{dictionary, ?DICT},
73                        {module, ?MODULE},
74                        {answer_errors, callback}]}]).
75
76-define(SUCCESS,
77        ?'DIAMETER_BASE_RESULT-CODE_SUCCESS').
78-define(MISSING_AVP,
79        ?'DIAMETER_BASE_RESULT-CODE_MISSING_AVP').
80-define(INVALID_MESSAGE_LENGTH,
81        ?'DIAMETER_BASE_RESULT-CODE_INVALID_MESSAGE_LENGTH').
82
83-define(LOGOUT,
84        ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT').
85
86-define(GROUPS, [exit, handle, discard]).
87
88-define(L, atom_to_list).
89
90%% ===========================================================================
91
92suite() ->
93    [{timetrap, {seconds, 60}}].
94
95all() ->
96    [{group, G} || G <- ?GROUPS].
97
98groups() ->
99    [{G, [], [start, send, stop]} || G <- ?GROUPS].
100
101init_per_suite(Config) ->
102    ok = diameter:start(),
103    Config.
104
105end_per_suite(_Config) ->
106    ok = diameter:stop().
107
108init_per_group(Group, Config) ->
109    [{group, Group} | Config].
110
111end_per_group(_, _) ->
112    ok.
113
114init_per_testcase(_Name, Config) ->
115    Config.
116
117end_per_testcase(_, _) ->
118    ok.
119
120origin(exit)    -> 0;
121origin(handle)  -> 1;
122origin(discard) -> 2;
123
124origin(0) -> exit;
125origin(1) -> handle;
126origin(2) -> discard.
127
128%% ===========================================================================
129
130%% start/1
131
132start(Config) ->
133    Group = proplists:get_value(group, Config),
134    ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group))),
135    ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)),
136    LRef = ?util:listen(?SERVER,
137                        tcp,
138                        [{length_errors, Group}]),
139    ?util:connect(?CLIENT,
140                  tcp,
141                  LRef,
142                  [{capabilities, [{'Origin-State-Id', origin(Group)}]}]).
143
144%% stop/1
145
146stop(_Config) ->
147    ok = diameter:remove_transport(?CLIENT, true),
148    ok = diameter:remove_transport(?SERVER, true),
149    ok = diameter:stop_service(?SERVER),
150    ok = diameter:stop_service(?CLIENT).
151
152%% send/1
153
154%% Server transport exits on messages of insuffient length.
155send(exit) ->
156    %% Transport exit is followed by failover but there's only one
157    %% transport to choose from.
158    {error, failover} = call(4);
159
160%% Server transport receives messages of insufficient length.
161send(handle) ->
162    %% Message Length too large: diameter_tcp flushes the request
163    %% when no additional bytes arrive.
164    #diameter_base_STA{'Result-Code' = ?INVALID_MESSAGE_LENGTH}
165        = call(4),
166    %% Another request answered as it should.
167    #diameter_base_STA{'Result-Code' = ?SUCCESS}
168        = call(0),
169    %% Message Length conveniently small: the trailing optional
170    %% Origin-State-Id isn't included in the received request.
171    #diameter_base_STA{'Result-Code' = ?SUCCESS}
172        = call(-12),
173    %% Server receives Origin-State-Id AVP as the first 12 bytes of
174    %% the next request: AVP <<Code:32, Flags:8, Len:24, Data:32>> is
175    %% interpreted as header <<Version:8, Len:24, Flags:8, Code:24,
176    %% ApplId: 32>>. In particular, the AVP Length 12 = 00001100 is
177    %% interpreted as Command Flags, so R=0 and the request is
178    %% interpreted as an unsolicited answer. Increase Message Length
179    %% to have the server receive all bytes sent thusfar.
180    {error, timeout}
181        = call(12),
182    %% Another request answered as it should.
183    #diameter_base_STA{'Result-Code' = ?SUCCESS}
184        = call(0),
185    %% Shorten Message Length so much that that the server doesn't
186    %% receive the required Termination-Cause AVP.
187    #diameter_base_STA{'Result-Code' = ?MISSING_AVP}
188        = call(-24);
189
190%% Server transport discards message of insufficient length.
191send(discard) ->
192    %% First request times out when the server discards it but a
193    %% second succeeds since the transport remains up.
194    {error, timeout}
195        = call(4),
196    #diameter_base_STA{'Result-Code' = ?SUCCESS}
197        = call(0);
198
199send(Config) ->
200    send(proplists:get_value(group, Config)).
201
202%% ===========================================================================
203
204call(Delta) ->
205    diameter:call(?CLIENT,
206                  ?DICT,
207                  #diameter_base_STR
208                  {'Termination-Cause' = ?LOGOUT,
209                   'Auth-Application-Id' = ?DIAMETER_APP_ID_COMMON,
210                   'Origin-State-Id' = [7]},
211                  [{extra, [Delta]}]).
212
213%% ===========================================================================
214%% diameter callbacks
215
216%% peer_up/3
217
218peer_up(_SvcName, _Peer, State) ->
219    State.
220
221%% peer_down/3
222
223peer_down(_SvcName, _Peer, State) ->
224    State.
225
226%% pick_peer/5
227
228pick_peer([Peer], _, ?CLIENT, _State, _Delta) ->
229    {ok, Peer}.
230
231%% prepare_request/4
232
233prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Delta) ->
234    {send, resize(Delta, prepare(Pkt, Caps))}.
235
236prepare(#diameter_packet{msg = Req0} = Pkt, Caps) ->
237    #diameter_caps{origin_host  = {OH, _},
238                   origin_realm = {OR, DR}}
239        = Caps,
240    Req = Req0#diameter_base_STR{'Session-Id' = diameter:session_id(OH),
241                                 'Origin-Host' = OH,
242                                 'Origin-Realm' = OR,
243                                 'Destination-Realm' = DR},
244    diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req}).
245
246resize(0, Pkt) ->
247    Pkt;
248resize(Delta, #diameter_packet{bin = Bin} = Pkt) ->
249    Pkt#diameter_packet{bin = resize(Delta, Bin)};
250
251resize(Delta, <<V, Len:24, T/binary>>) ->
252    <<V, (Len + Delta):24, T/binary>>.
253
254%% handle_answer/5
255
256handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Delta) ->
257    Pkt#diameter_packet.msg.
258
259%% handle_error/5
260
261handle_error(Reason, _Req, ?CLIENT, _Peer, _Delta) ->
262    {error, Reason}.
263
264%% handle_request/3
265
266handle_request(Pkt, ?SERVER, {_Ref, Caps}) ->
267    #diameter_caps{origin_host  = {OH, _},
268                   origin_realm = {OR, _},
269                   origin_state_id = {_,[Id]}}
270        = Caps,
271    answer(origin(Id),
272           Pkt,
273           #diameter_base_STA{'Result-Code' = ?SUCCESS,
274                              'Session-Id' = diameter:session_id(OH),
275                              'Origin-Host' = OH,
276                              'Origin-Realm' = OR}).
277
278answer(Group, #diameter_packet{errors = Es}, Ans) ->
279    answer(Group, Es, Ans);
280
281%% No errors: just answer.
282answer(_, [], Ans) ->
283    {reply, Ans};
284
285%% Otherwise an invalid length should only reach the callback if
286%% length_errors = handle.
287answer(Group, [RC|_], Ans)
288  when RC == ?INVALID_MESSAGE_LENGTH, Group == handle;
289       RC /= ?INVALID_MESSAGE_LENGTH ->
290    {reply, Ans}.
291