1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2010-2019. 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%% A diameter callback module that can redirect selected callbacks,
23%% providing reasonable default implementations otherwise.
24%%
25%% To order alternate callbacks, configure a #diameter_callback record
26%% as the Diameter application callback in question. The record has
27%% one field for each callback function as well as 'default' and
28%% 'extra' fields. A function-specific field can be set to a
29%% diameter:eval() in order to redirect the callback
30%% corresponding to that field, or to 'false' to request the default
31%% callback implemented in this module. If neither of these fields are
32%% set then the 'default' field determines the form of the callback: a
33%% module name results in the usual callback as if the module had been
34%% configured directly as the callback module, a diameter_eval()
35%% in a callback applied to the atom-valued callback name and argument
36%% list. For all callbacks not to this module, the 'extra' field is a
37%% list of additional arguments, following arguments supplied by
38%% diameter but preceding those of the diameter:eval() being
39%% applied.
40%%
41%% For example, the following config to diameter:start_service/2, in
42%% an 'application' tuple, would result in only a mymod:peer_down/3
43%% callback, this module implementing the remaining callbacks.
44%%
45%%   {module, #diameter_callback{peer_down = {mymod, down, []}}}
46%%
47%% Equivalently, this can also be specified with a [Mod | Args]
48%% field/value list as follows.
49%%
50%%   {module, [diameter_callback, {peer_down, {mymod, down, []}}]}
51%%
52%% The following would result in this module suppying peer_up and
53%% peer_down callback, others taking place in module mymod.
54%%
55%%   {module, #diameter_callback{peer_up = false,
56%%                               peer_down = false,
57%%                               default = mymod}}
58%%
59%% The following would result in all callbacks taking place as
60%% calls to mymod:diameter/2.
61%%
62%%   {module, #diameter_callback{default = {mymod, diameter, []}}}
63%%
64%% The following are equivalent and result in all callbacks being
65%% provided by this module.
66%%
67%%   {module, #diameter_callback{}}
68%%   {module, diameter_callback}
69%%
70
71-module(diameter_callback).
72
73%% Default callbacks when no alternate is specified.
74-export([peer_up/3,
75         peer_down/3,
76         pick_peer/4,
77         prepare_request/3,
78         prepare_retransmit/3,
79         handle_request/3,
80         handle_answer/4,
81         handle_error/4]).
82
83%% Callbacks taking a #diameter_callback record.
84-export([peer_up/4,
85         peer_down/4,
86         pick_peer/5,
87         prepare_request/4,
88         prepare_retransmit/4,
89         handle_request/4,
90         handle_answer/5,
91         handle_error/5]).
92
93-include_lib("diameter/include/diameter.hrl").
94
95%%% ----------------------------------------------------------
96%%% # peer_up/3
97%%% ----------------------------------------------------------
98
99peer_up(_Svc, _Peer, State) ->
100    State.
101
102peer_up(Svc, Peer, State, D) ->
103    cb(peer_up,
104       [Svc, Peer, State],
105       D#diameter_callback.peer_up,
106       D).
107
108%%% ----------------------------------------------------------
109%%% # peer_down/3
110%%% ----------------------------------------------------------
111
112peer_down(_Svc, _Peer, State) ->
113    State.
114
115peer_down(Svc, Peer, State, D) ->
116    cb(peer_down,
117       [Svc, Peer, State],
118       D#diameter_callback.peer_down,
119       D).
120
121%%% ----------------------------------------------------------
122%%% # pick_peer/4
123%%% ----------------------------------------------------------
124
125pick_peer([Peer|_], _, _Svc, _State) ->
126    {ok, Peer};
127pick_peer([], _, _Svc, _State) ->
128    false.
129
130pick_peer(PeersL, PeersR, Svc, State, D) ->
131    cb(pick_peer,
132       [PeersL, PeersR, Svc, State],
133       D#diameter_callback.pick_peer,
134       D).
135
136%%% ----------------------------------------------------------
137%%% # prepare_request/3
138%%% ----------------------------------------------------------
139
140prepare_request(Pkt, _Svc, _Peer) ->
141    {send, Pkt}.
142
143prepare_request(Pkt, Svc, Peer, D) ->
144    cb(prepare_request,
145       [Pkt, Svc, Peer],
146       D#diameter_callback.prepare_request,
147       D).
148
149%%% ----------------------------------------------------------
150%%% # prepare_retransmit/3
151%%% ----------------------------------------------------------
152
153prepare_retransmit(Pkt, _Svc, _Peer) ->
154    {send, Pkt}.
155
156prepare_retransmit(Pkt, Svc, Peer, D) ->
157    cb(prepare_retransmit,
158       [Pkt, Svc, Peer],
159       D#diameter_callback.prepare_retransmit,
160       D).
161
162%%% ----------------------------------------------------------
163%%% # handle_request/3
164%%% ----------------------------------------------------------
165
166handle_request(_Pkt, _Svc, _Peer) ->
167    {protocol_error, 3001}.  %% DIAMETER_COMMAND_UNSUPPORTED
168
169handle_request(Pkt, Svc, Peer, D) ->
170    cb(handle_request,
171       [Pkt, Svc, Peer],
172       D#diameter_callback.handle_request,
173       D).
174
175%%% ----------------------------------------------------------
176%%% # handle_answer/4
177%%% ----------------------------------------------------------
178
179handle_answer(#diameter_packet{msg = Ans, errors = []}, _Req, _Svc, _Peer) ->
180    Ans;
181handle_answer(#diameter_packet{msg = Ans, errors = Es}, _Req, _Svc, _Peer) ->
182    [Ans | Es].
183
184handle_answer(Pkt, Req, Svc, Peer, D) ->
185    cb(handle_answer,
186       [Pkt, Req, Svc, Peer],
187       D#diameter_callback.handle_answer,
188       D).
189
190%%% ---------------------------------------------------------------------------
191%%% # handle_error/4
192%%% ---------------------------------------------------------------------------
193
194handle_error(Reason, _Req, _Svc, _Peer) ->
195    {error, Reason}.
196
197handle_error(Reason, Req, Svc, Peer, D) ->
198    cb(handle_error,
199       [Reason, Req, Svc, Peer],
200       D#diameter_callback.handle_error,
201       D).
202
203%% ===========================================================================
204
205%% cb/4
206
207%% Unspecified callback: use default field to determine something
208%% appropriate.
209cb(CB, Args, undefined, D) ->
210    cb(CB, Args, D);
211
212%% Explicitly requested default.
213cb(CB, Args, false, _) ->
214    apply(?MODULE, CB, Args);
215
216%% A specified callback.
217cb(_, Args, F, #diameter_callback{extra = X}) ->
218    diameter_lib:eval([[F|X] | Args]).
219
220%% cb/3
221
222%% No user-supplied default: call ours.
223cb(CB, Args, #diameter_callback{default = undefined}) ->
224    apply(?MODULE, CB, Args);
225
226%% Default is a module name: make the usual callback.
227cb(CB, Args, #diameter_callback{default = M,
228                                extra = X})
229  when is_atom(M) ->
230    apply(M, CB, Args ++ X);
231
232%% Default is something else: apply if to callback name and arguments.
233cb(CB, Args, #diameter_callback{default = F,
234                                extra = X}) ->
235    diameter_lib:eval([F, CB, Args | X]).
236