1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2013-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%% Implements the handling of incoming and outgoing Diameter messages
23%% except CER/CEA, DWR/DWA and DPR/DPA. That is, the messages that a
24%% diameter client sends and receives.
25%%
26
27-module(diameter_traffic).
28
29%% towards diameter
30-export([send_request/4]).
31
32%% towards diameter_watchdog
33-export([receive_message/5]).
34
35%% towards diameter_peer_fsm and diameter_watchdog
36-export([incr/4,
37         incr_error/4,
38         incr_rc/4]).
39
40%% towards diameter_service
41-export([make_recvdata/1,
42         peer_up/1,
43         peer_down/1]).
44
45%% towards diameter_dist
46-export([request_info/1]).
47
48%% internal
49-export([send/1,    %% send from remote node
50         request/1, %% process request in handler process
51         init/1]).  %% monitor process start
52
53-include_lib("diameter/include/diameter.hrl").
54-include("diameter_internal.hrl").
55
56-define(LOGX(Reason, T), begin ?LOG(Reason, T), x({Reason, T}) end).
57
58-define(RELAY, ?DIAMETER_DICT_RELAY).
59-define(BASE,  ?DIAMETER_DICT_COMMON).  %% Note: the RFC 3588 dictionary
60
61-define(DEFAULT(V, Def), if V == undefined -> Def; true -> V end).
62
63%% Table containing outgoing entries that live and die with
64%% peer_up/down. The name is historic, since the table used to contain
65%% information about outgoing requests for which an answer has yet to
66%% be received.
67-define(REQUEST_TABLE, diameter_request).
68
69%% Record diameter:call/4 options are parsed into.
70-record(options,
71        {peers = []     :: [diameter:peer_ref()],
72         filter = none  :: diameter:peer_filter(),
73         extra = []     :: list(),
74         timeout = 5000 :: 0..16#FFFFFFFF,  %% for outgoing requests
75         detach = false :: boolean()}).
76
77%% Term passed back to receive_message/5 with every incoming message.
78-record(recvdata,
79        {peerT        :: ets:tid(),
80         service_name :: diameter:service_name(),
81         apps         :: [#diameter_app{}],
82         sequence     :: diameter:sequence(),
83         counters     :: boolean(),
84         codec        :: #{decode_format := diameter:decode_format(),
85                           avp_dictionaries => nonempty_list(module()),
86                           string_decode := boolean(),
87                           strict_arities => diameter:strict_arities(),
88                           strict_mbit := boolean(),
89                           incoming_maxlen := diameter:message_length()}}).
90%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
91%% so that any message exceeding the maximum is discarded. Retain the
92%% option in case we want to extend the values and semantics.
93
94%% Record stored in diameter_request for each outgoing request.
95-record(request,
96        {ref        :: reference(),         %% used to receive answer
97         caller     :: pid() | undefined,   %% calling process
98         handler    :: pid(),               %% request process
99         peer       :: undefined | {pid(), #diameter_caps{}},
100         caps       :: undefined,           %% no longer used
101         packet     :: #diameter_packet{} | undefined}). %% of request
102
103%% ---------------------------------------------------------------------------
104%% make_recvdata/1
105%% ---------------------------------------------------------------------------
106
107make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
108    #{sequence := {_,_} = Mask, spawn_opt := Opts, traffic_counters := B}
109        = SvcOpts,
110    {Opts, #recvdata{service_name = SvcName,
111                     peerT = PeerT,
112                     apps = Apps,
113                     sequence = Mask,
114                     counters = B,
115                     codec = maps:with([decode_format,
116                                        avp_dictionaries,
117                                        string_decode,
118                                        strict_arities,
119                                        strict_mbit,
120                                        ordered_encode,
121                                        incoming_maxlen],
122                                       SvcOpts)}}.
123
124%% ---------------------------------------------------------------------------
125%% peer_up/1
126%% ---------------------------------------------------------------------------
127
128%% Start a process that dies with peer_down/1, on which request
129%% processes can monitor. There is no other process that dies with
130%% peer_down since failover doesn't imply the loss of transport in the
131%% case of a watchdog transition into state SUSPECT.
132peer_up(TPid) ->
133    proc_lib:start(?MODULE, init, [TPid]).
134
135init(TPid) ->
136    ets:insert(?REQUEST_TABLE, {TPid, self()}),
137    proc_lib:init_ack(self()),
138    proc_lib:hibernate(erlang, exit, [{shutdown, TPid}]).
139
140%% ---------------------------------------------------------------------------
141%% peer_down/1
142%% ---------------------------------------------------------------------------
143
144peer_down(TPid) ->
145    [{_, Pid}] = ets:lookup(?REQUEST_TABLE, TPid),
146    ets:delete(?REQUEST_TABLE, TPid),
147    Pid ! ok,  %% make it die
148    Pid.
149
150%% ---------------------------------------------------------------------------
151%% incr/4
152%% ---------------------------------------------------------------------------
153
154incr(Dir, #diameter_packet{header = H}, TPid, AppDict) ->
155    incr(Dir, H, TPid, AppDict);
156
157incr(Dir, #diameter_header{} = H, TPid, AppDict) ->
158    incr(TPid, {msg_id(H, AppDict), Dir}).
159
160%% ---------------------------------------------------------------------------
161%% incr_error/4
162%% ---------------------------------------------------------------------------
163
164%% Identify messages using the application dictionary, not the encode
165%% dictionary, which may differ in the case of answer-message.
166incr_error(Dir, T, Pid, {_MsgDict, AppDict}) ->
167    incr_error(Dir, T, Pid, AppDict);
168
169%% Decoded message without errors.
170incr_error(recv, #diameter_packet{errors = []}, _, _) ->
171    ok;
172
173incr_error(recv = D, #diameter_packet{header = H}, TPid, AppDict) ->
174    incr_error(D, H, TPid, AppDict);
175
176%% Encoded message with errors and an identifiable header ...
177incr_error(send = D, {_, _, #diameter_header{} = H}, TPid, AppDict) ->
178    incr_error(D, H, TPid, AppDict);
179
180%% ... or not.
181incr_error(send = D, {_,_}, TPid, _) ->
182    incr_error(D, unknown, TPid);
183
184incr_error(Dir, #diameter_header{} = H, TPid, AppDict) ->
185    incr_error(Dir, msg_id(H, AppDict), TPid);
186
187incr_error(Dir, Id, TPid, _) ->
188    incr_error(Dir, Id, TPid).
189
190incr_error(Dir, Id, TPid) ->
191    incr(TPid, {Id, Dir, error}).
192
193%% ---------------------------------------------------------------------------
194%% incr_rc/4
195%% ---------------------------------------------------------------------------
196
197-spec incr_rc(send|recv, Pkt, TPid, DictT)
198   -> Counter
199    | Reason
200 when Pkt :: #diameter_packet{},
201      TPid :: pid(),
202      DictT :: module() | {MsgDict :: module(),
203                           AppDict :: module(),
204                           CommonDict:: module()},
205      Counter :: {'Result-Code', integer()}
206               | {'Experimental-Result', integer(), integer()},
207      Reason :: atom().
208
209incr_rc(Dir, Pkt, TPid, {MsgDict, AppDict, Dict0}) ->
210    incr_rc(Dir, Pkt, TPid, MsgDict, AppDict, Dict0);
211
212incr_rc(Dir, Pkt, TPid, Dict0) ->
213    incr_rc(Dir, Pkt, TPid, Dict0, Dict0, Dict0).
214
215%% incr_rc/6
216
217incr_rc(Dir, Pkt, TPid, MsgDict, AppDict, Dict0) ->
218    try get_result(Dir, MsgDict, Dict0, Pkt) of
219        false ->
220            unknown;
221        Avp ->
222            incr_result(Dir, Avp, Pkt, TPid, AppDict)
223    catch
224        exit: {E,_} when E == no_result_code;
225                         E == invalid_error_bit ->
226            incr(TPid, {msg_id(Pkt#diameter_packet.header, AppDict), Dir, E}),
227            E
228    end.
229
230%% ---------------------------------------------------------------------------
231%% receive_message/5
232%%
233%% Handle an incoming Diameter message in a watchdog process.
234%% ---------------------------------------------------------------------------
235
236-spec receive_message(pid(), Route, #diameter_packet{}, module(), RecvData)
237   -> pid()     %% request handler
238    | boolean() %% answer, known request or not
239    | discard   %% request discarded
240 when Route :: {Handler, RequestRef, TPid}
241             | Ack,
242      RecvData :: {[SpawnOpt], #recvdata{}},
243      SpawnOpt :: term(),
244      Handler :: pid(),
245      RequestRef :: reference(),
246      TPid :: pid(),
247      Ack :: boolean().
248
249receive_message(TPid, Route, Pkt, Dict0, RecvData) ->
250    #diameter_packet{header = #diameter_header{is_request = R}} = Pkt,
251    recv(R, Route, TPid, Pkt, Dict0, RecvData).
252
253%% recv/6
254
255%% Incoming request ...
256recv(true, Ack, TPid, Pkt, Dict0, T)
257  when is_boolean(Ack) ->
258    {Opts, RecvData} = T,
259    AppT = find_app(TPid, Pkt, RecvData),
260    ack(Ack, TPid, spawn_request(AppT, Opts, Ack, TPid, Pkt, Dict0, RecvData));
261
262%% ... answer to known request ...
263recv(false, {Pid, Ref, TPid}, _, Pkt, Dict0, _) ->
264    Pid ! {answer, Ref, TPid, Dict0, Pkt},
265    true;
266
267%% Note that failover could have happened prior to this message being
268%% received and triggering failback. That is, both a failover message
269%% and answer may be on their way to the handler process. In the worst
270%% case the request process gets notification of the failover and
271%% sends to the alternate peer before an answer arrives, so it's
272%% always the case that we can receive more than one answer after
273%% failover. The first answer received by the request process wins,
274%% any others are discarded.
275
276%% ... or not.
277recv(false, false, TPid, Pkt, _, _) ->
278    ?LOG(discarded, Pkt#diameter_packet.header),
279    incr(TPid, {{unknown, 0}, recv, discarded}),
280    false.
281
282%% spawn_request/7
283
284spawn_request(false, _, _, _, _, _, _) ->  %% no transport
285    discard;
286
287%% An MFA should return the pid() of a process in which the argument
288%% fun in applied, or the atom 'discard' if the fun is not applied.
289%% The latter results in an acknowledgment back to the transport
290%% process when appropriate, to ensure that send/recv callbacks can
291%% count outstanding requests. Acknowledgement is implicit if the
292%% handler process dies (in a handle_request callback for example).
293spawn_request(AppT, {M,F,A}, Ack, TPid, Pkt, Dict0, RecvData) ->
294    %% Term to pass to request/1 in an appropriate process. Module
295    %% diameter_dist implements callbacks.
296    ReqT = {Pkt, AppT, Ack, TPid, Dict0, RecvData},
297    apply(M, F, [ReqT | A]);
298
299%% A spawned process acks implicitly when it dies, so there's no need
300%% to handle 'discard'.
301spawn_request(AppT, Opts, Ack, TPid, Pkt, Dict0, RecvData) ->
302    spawn_opt(fun() ->
303                      recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT)
304              end,
305              Opts).
306
307%% request_info/1
308%%
309%% Limited request information for diameter_dist.
310
311request_info({Pkt, _AppT, _Ack, _TPid, _Dict0, RecvData} = _ReqT) ->
312    {RecvData#recvdata.service_name, Pkt#diameter_packet.bin}.
313
314%% request/1
315%%
316%% Called from a handler process chosen by a transport spawn_opt MFA
317%% to process an incoming request.
318
319request({Pkt, AppT, Ack, TPid, Dict0, RecvData} = _ReqT) ->
320    ack(Ack, TPid, recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT)).
321
322%% ack/3
323
324ack(Ack, TPid, RC) ->
325    RC == discard
326        andalso Ack
327        andalso (TPid ! {send, false}),
328    RC.
329
330%% ---------------------------------------------------------------------------
331%% recv_request/6
332%% ---------------------------------------------------------------------------
333
334-spec recv_request(Ack :: boolean(),
335                   TPid :: pid(),
336                   #diameter_packet{},
337                   Dict0 :: module(),
338                   #recvdata{},
339                   AppT :: {#diameter_app{}, #diameter_caps{}}
340                         | #diameter_caps{}) %% no suitable app
341   -> ok        %% answer was sent
342    | discard.  %% or not
343
344recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT) ->
345    Ack andalso (TPid ! {handler, self()}),
346    case AppT of
347        {#diameter_app{id = Aid, dictionary = AppDict} = App, Caps} ->
348            Count = RecvData#recvdata.counters,
349            Count andalso incr(recv, Pkt, TPid, AppDict),
350            DecPkt = decode(Aid, AppDict, RecvData, Pkt),
351            Count andalso incr_error(recv, DecPkt, TPid, AppDict),
352            send_A(recv_R(App, TPid, Dict0, Caps, RecvData, DecPkt),
353                   TPid,
354                   App,
355                   Dict0,
356                   RecvData,
357                   DecPkt,
358                   Caps);
359        #diameter_caps{} = Caps ->
360            %%   DIAMETER_APPLICATION_UNSUPPORTED   3007
361            %%      A request was sent for an application that is not
362            %%      supported.
363            RC = 3007,
364            DecPkt = diameter_codec:collect_avps(Pkt),
365            send_answer(answer_message(RC, Dict0, Caps, DecPkt),
366                        TPid,
367                        Dict0,
368                        Dict0,
369                        Dict0,
370                        RecvData,
371                        DecPkt,
372                        [[]])
373    end.
374
375%% find_app/3
376%%
377%% Lookup the application of a received Diameter request on the node
378%% on which it's received.
379
380find_app(TPid,
381         #diameter_packet{header = #diameter_header{application_id = Id}},
382         #recvdata{peerT = PeerT,
383                   apps = Apps}) ->
384    diameter_service:find_incoming_app(PeerT, TPid, Id, Apps).
385
386%% decode/4
387
388decode(Id, Dict, #recvdata{codec = Opts}, Pkt) ->
389    errors(Id, diameter_codec:decode(Id, Dict, Opts, Pkt)).
390
391%% send_A/7
392
393send_A([T | Fs], TPid, App, Dict0, RecvData, DecPkt, Caps) ->
394    send_A(T, TPid, App, Dict0, RecvData, DecPkt, Caps, Fs);
395
396send_A(discard = No, _, _, _, _, _, _) ->
397    No.
398
399%% recv_R/6
400
401%% Answer errors ourselves ...
402recv_R(#diameter_app{options = [_, {request_errors, E} | _]},
403       _TPid,
404       Dict0,
405       _Caps,
406       _RecvData,
407       #diameter_packet{errors = [RC|_]})  %% a detected 3xxx is hd
408  when E == answer, Dict0 /= ?BASE orelse 3 == RC div 1000;
409       E == answer_3xxx, 3 == RC div 1000 ->
410    [{answer_message, rc(RC)}, []];
411
412%% ... or make a handle_request callback. Note that
413%% Pkt#diameter_packet.msg = undefined in the 3001 case.
414recv_R(App,
415       TPid,
416       _Dict0,
417       Caps,
418       #recvdata{service_name = SvcName},
419       Pkt) ->
420    request_cb(cb(App, handle_request, [Pkt, SvcName, {TPid, Caps}]),
421               App,
422               [],
423               []).
424
425rc({N,_}) ->
426    N;
427rc(N) ->
428    N.
429
430%% errors/1
431%%
432%% Look for additional errors in a decoded message, prepending the
433%% errors field with the first detected error. It's odd/unfortunate
434%% that 501[15] aren't protocol errors. With RFC 3588 this means that
435%% a handle_request callback has to formulate the answer. With RFC
436%% 6733 it's acceptable for 5xxx to be sent in an answer-message.
437
438%%   DIAMETER_INVALID_MESSAGE_LENGTH 5015
439%%      This error is returned when a request is received with an invalid
440%%      message length.
441
442errors(_, #diameter_packet{header = #diameter_header{length = Len} = H,
443                           bin = Bin,
444                           errors = Es}
445          = Pkt)
446  when Len < 20;
447       0 /= Len rem 4;
448       8*Len /= bit_size(Bin) ->
449    ?LOG(invalid_message_length, {H, bit_size(Bin)}),
450    Pkt#diameter_packet{errors = [5015 | Es]};
451
452%%   DIAMETER_UNSUPPORTED_VERSION       5011
453%%      This error is returned when a request was received, whose version
454%%      number is unsupported.
455
456errors(_, #diameter_packet{header = #diameter_header{version = V} = H,
457                           errors = Es}
458          = Pkt)
459  when V /= ?DIAMETER_VERSION ->
460    ?LOG(unsupported_version, H),
461    Pkt#diameter_packet{errors = [5011 | Es]};
462
463%%   DIAMETER_COMMAND_UNSUPPORTED       3001
464%%      The Request contained a Command-Code that the receiver did not
465%%      recognize or support.  This MUST be used when a Diameter node
466%%      receives an experimental command that it does not understand.
467
468errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P} = H,
469                            msg = M,
470                            errors = Es}
471           = Pkt)
472  when ?APP_ID_RELAY /= Id, undefined == M;  %% don't know the command
473       ?APP_ID_RELAY == Id, not P ->         %% command isn't proxiable
474    ?LOG(command_unsupported, H),
475    Pkt#diameter_packet{errors = [3001 | Es]};
476
477%%   DIAMETER_INVALID_HDR_BITS          3008
478%%      A request was received whose bits in the Diameter header were
479%%      either set to an invalid combination, or to a value that is
480%%      inconsistent with the command code's definition.
481
482errors(_, #diameter_packet{header = #diameter_header{is_request = true,
483                                                     is_error = true}
484                                  = H,
485                            errors = Es}
486          = Pkt) ->
487    ?LOG(invalid_hdr_bits, H),
488    Pkt#diameter_packet{errors = [3008 | Es]};
489
490%% Green.
491errors(_, Pkt) ->
492    Pkt.
493
494%% request_cb/4
495
496%% A reply may be an answer-message, constructed either here or by
497%% the handle_request callback. The header from the incoming request
498%% is passed into the encode so that it can retrieve the relevant
499%% command code in this case. It will also then ignore Dict and use
500%% the base encoder.
501request_cb({reply, _Ans} = T, _App, EvalPktFs, EvalFs) ->
502    [T, EvalPktFs | EvalFs];
503
504%% An 3xxx result code, for which the E-bit is set in the header.
505request_cb({protocol_error, RC}, _App, EvalPktFs, EvalFs)
506  when 3 == RC div 1000 ->
507    [{answer_message, RC}, EvalPktFs | EvalFs];
508
509request_cb({answer_message, RC} = T, _App, EvalPktFs, EvalFs)
510  when 3 == RC div 1000;
511       5 == RC div 1000 ->
512    [T, EvalPktFs | EvalFs];
513
514%% RFC 3588 says we must reply 3001 to anything unrecognized or
515%% unsupported. 'noreply' is undocumented (and inappropriately named)
516%% backwards compatibility for this, protocol_error the documented
517%% alternative.
518request_cb(noreply, _App, EvalPktFs, EvalFs) ->
519    [{answer_message, 3001}, EvalPktFs | EvalFs];
520
521%% Relay a request to another peer. This is equivalent to doing an
522%% explicit call/4 with the message in question except that (1) a loop
523%% will be detected by examining Route-Record AVP's, (3) a
524%% Route-Record AVP will be added to the outgoing request and (3) the
525%% End-to-End Identifier will default to that in the
526%% #diameter_header{} without the need for an end_to_end_identifier
527%% option.
528%%
529%% relay and proxy are similar in that they require the same handling
530%% with respect to Route-Record and End-to-End identifier. The
531%% difference is that a proxy advertises specific applications, while
532%% a relay advertises the relay application. If a callback doesn't
533%% want to distinguish between the cases in the callback return value
534%% then 'resend' is a neutral alternative.
535%%
536request_cb({A, Opts}, #diameter_app{id = Id}, EvalPktFs, EvalFs)
537  when A == relay, Id == ?APP_ID_RELAY;
538       A == proxy, Id /= ?APP_ID_RELAY;
539       A == resend ->
540    [{call, Opts}, EvalPktFs | EvalFs];
541
542request_cb(discard = No, _, _, _) ->
543    No;
544
545request_cb({eval_packet, RC, F}, App, Fs, EvalFs) ->
546    request_cb(RC, App, [F|Fs], EvalFs);
547
548request_cb({eval, RC, F}, App, EvalPktFs, Fs) ->
549    request_cb(RC, App, EvalPktFs, [F|Fs]);
550
551request_cb(T, App, _, _) ->
552    ?ERROR({invalid_return, T, handle_request, App}).
553
554%% send_A/8
555
556send_A({reply, Ans}, TPid, App, Dict0, RecvData, Pkt, _Caps, Fs) ->
557    AppDict = App#diameter_app.dictionary,
558    MsgDict = msg_dict(AppDict, Dict0, Ans),
559    send_answer(Ans,
560                TPid,
561                MsgDict,
562                AppDict,
563                Dict0,
564                RecvData,
565                Pkt,
566                Fs);
567
568send_A({call, Opts}, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) ->
569    AppDict = App#diameter_app.dictionary,
570    case resend(Opts, Caps, Pkt, App, Dict0, RecvData) of
571        #diameter_packet{bin = Bin} = Ans -> %% answer: reset hop by hop id
572            #diameter_packet{header = #diameter_header{hop_by_hop_id = Id},
573                             transport_data = TD}
574                = Pkt,
575            Reset = diameter_codec:hop_by_hop_id(Id, Bin),
576            MsgDict = msg_dict(AppDict, Dict0, Ans),
577            send_answer(Ans#diameter_packet{bin = Reset,
578                                            transport_data = TD},
579                        TPid,
580                        MsgDict,
581                        AppDict,
582                        Dict0,
583                        RecvData#recvdata.counters,
584                        Fs);
585        RC ->
586            send_answer(answer_message(RC, Dict0, Caps, Pkt),
587                        TPid,
588                        Dict0,
589                        AppDict,
590                        Dict0,
591                        RecvData,
592                        Pkt,
593                        Fs)
594    end;
595
596%% RFC 3588 only allows 3xxx errors in an answer-message. RFC 6733
597%% added the possibility of setting 5xxx.
598
599send_A({answer_message, RC} = T, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) ->
600    Dict0 /= ?BASE orelse 3 == RC div 1000
601        orelse ?ERROR({invalid_return, T, handle_request, App}),
602    send_answer(answer_message(RC, Dict0, Caps, Pkt),
603                TPid,
604                Dict0,
605                App#diameter_app.dictionary,
606                Dict0,
607                RecvData,
608                Pkt,
609                Fs).
610
611%% send_answer/8
612
613%% Skip the setting of Result-Code and Failed-AVP's below. This is
614%% undocumented and shouldn't be relied on.
615send_answer([Ans], TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs)
616  when [] == Pkt#diameter_packet.errors ->
617    send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs);
618send_answer([Ans], TPid, MsgDict, AppDict, Dict0, RecvData, Pkt0, Fs) ->
619    Pkt = Pkt0#diameter_packet{errors = []},
620    send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs);
621
622send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, DecPkt, Fs) ->
623    Pkt = encode({MsgDict, AppDict},
624                 TPid,
625                 RecvData#recvdata.codec,
626                 make_answer_packet(Ans, DecPkt, MsgDict, Dict0)),
627    send_answer(Pkt,
628                TPid,
629                MsgDict,
630                AppDict,
631                Dict0,
632                RecvData#recvdata.counters,
633                Fs).
634
635%% send_answer/7
636
637send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, Count, [EvalPktFs | EvalFs]) ->
638    eval_packet(Pkt, EvalPktFs),
639    Count andalso begin
640                      incr(send, Pkt, TPid, AppDict),
641                      incr_rc(send, Pkt, TPid, MsgDict, AppDict, Dict0)
642                  end,
643    send(TPid, z(Pkt), _Route = self()),
644    lists:foreach(fun diameter_lib:eval/1, EvalFs).
645
646%% msg_dict/3
647%%
648%% Return the dictionary defining the message grammar in question: the
649%% application dictionary or the common dictionary.
650
651msg_dict(AppDict, Dict0, [Msg]) ->
652    msg_dict(AppDict, Dict0, Msg);
653
654msg_dict(AppDict, Dict0, Msg) ->
655    choose(is_answer_message(Msg, Dict0), Dict0, AppDict).
656
657%% Incoming, not yet decoded.
658is_answer_message(#diameter_packet{header = #diameter_header{} = H,
659                                   msg = undefined},
660                  Dict0) ->
661    is_answer_message([H], Dict0);
662
663is_answer_message(#diameter_packet{msg = Msg}, Dict0) ->
664    is_answer_message(Msg, Dict0);
665
666%% Message sent as a header/avps list.
667is_answer_message([#diameter_header{is_request = R, is_error = E} | _], _) ->
668    E andalso not R;
669
670%% Message sent as a map or tagged avp/value list.
671is_answer_message([Name | _], _) ->
672    Name == 'answer-message';
673
674%% Message sent as a record.
675is_answer_message(Rec, Dict) ->
676    try
677        'answer-message' == Dict:rec2msg(element(1,Rec))
678    catch
679        error:_ -> false
680    end.
681
682%% resend/6
683
684resend(Opts, Caps, Pkt, App, Dict0, RecvData) ->
685    resend(is_loop(Dict0, Caps, Pkt), Opts, Caps, Pkt, App, Dict0, RecvData).
686
687%% resend/7
688
689%%   DIAMETER_LOOP_DETECTED             3005
690%%      An agent detected a loop while trying to get the message to the
691%%      intended recipient.  The message MAY be sent to an alternate peer,
692%%      if one is available, but the peer reporting the error has
693%%      identified a configuration problem.
694
695resend(true, _Opts, _Caps, _Pkt, _App, _Dict0, _RecvData) ->
696    3005;
697
698%% 6.1.8.  Relaying and Proxying Requests
699%%
700%%   A relay or proxy agent MUST append a Route-Record AVP to all requests
701%%   forwarded.  The AVP contains the identity of the peer the request was
702%%   received from.
703
704resend(false,
705       Opts,
706       #diameter_caps{origin_host = {_,OH}},
707       #diameter_packet{header = Hdr0,
708                        avps = Avps},
709       App,
710       Dict0,
711       #recvdata{service_name = SvcName,
712                 sequence = Mask}) ->
713    Route = #diameter_avp{data = {Dict0, 'Route-Record', OH}},
714    Seq = diameter_session:sequence(Mask),
715    Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq},
716    Msg = [Hdr | Avps ++ [Route]],
717    case send_request(SvcName, App, Msg, Opts) of
718        #diameter_packet{} = Ans ->
719            Ans;
720        _ ->
721            3002  %% DIAMETER_UNABLE_TO_DELIVER.
722    end.
723%% The incoming request is relayed with the addition of a
724%% Route-Record. Note the requirement on the return from call/4 below,
725%% which places a requirement on the value returned by the
726%% handle_answer callback of the application module in question.
727%%
728%% Note that there's nothing stopping the request from being relayed
729%% back to the sender. A pick_peer callback may want to avoid this but
730%% a smart peer might recognize the potential loop and choose another
731%% route. A less smart one will probably just relay the request back
732%% again and force us to detect the loop. A pick_peer that wants to
733%% avoid this can specify filter to avoid the possibility.
734%% Eg. {neg, {host, OH} where #diameter_caps{origin_host = {OH, _}}.
735%%
736%% RFC 6.3 says that a relay agent does not modify Origin-Host but
737%% says nothing about a proxy. Assume it should behave the same way.
738
739%% is_loop/3
740
741is_loop(Dict0,
742        #diameter_caps{origin_host = {OH,_}},
743        #diameter_packet{avps = Avps}) ->
744    {Code, _Flags, Vid} = Dict0:avp_header('Route-Record'),
745    is_loop(Code, Vid, OH, Avps).
746
747%% is_loop/4
748%%
749%% Is there a Route-Record AVP with our Origin-Host?
750
751is_loop(Code, Vid, Bin, [#diameter_avp{code = Code,
752                                       vendor_id = Vid,
753                                       data = Bin}
754                         | _]) ->
755    true;
756
757is_loop(_, _, _, []) ->
758    false;
759
760is_loop(Code, Vid, OH, [_ | Avps])
761  when is_binary(OH) ->
762    is_loop(Code, Vid, OH, Avps);
763
764is_loop(Code, Vid, OH, Avps) ->
765    is_loop(Code, Vid, list_to_binary(OH), Avps).
766
767%% select_error/3
768%%
769%% Extract the first appropriate RC or {RC, #diameter_avp{}}
770%% pair from an errors list, along with any leading #diameter_avp{}.
771%%
772%% RFC 6733:
773%%
774%%  7.5.  Failed-AVP AVP
775%%
776%%   The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides
777%%   debugging information in cases where a request is rejected or not
778%%   fully processed due to erroneous information in a specific AVP.  The
779%%   value of the Result-Code AVP will provide information on the reason
780%%   for the Failed-AVP AVP.  A Diameter answer message SHOULD contain an
781%%   instance of the Failed-AVP AVP that corresponds to the error
782%%   indicated by the Result-Code AVP.  For practical purposes, this
783%%   Failed-AVP would typically refer to the first AVP processing error
784%%   that a Diameter node encounters.
785%%
786%% 3xxx can only be set in an answer setting the E-bit. RFC 6733 also
787%% allows 5xxx, RFC 3588 doesn't.
788
789select_error(E, Es, Dict0) ->
790    select(E, Es, Dict0, []).
791
792%% select/4
793
794select(E, [{RC, _} = T | Es], Dict0, Avps) ->
795    select(E, RC, T, Es, Dict0, Avps);
796
797select(E, [#diameter_avp{} = A | Es], Dict0, Avps) ->
798    select(E, Es, Dict0, [A | Avps]);
799
800select(E, [RC | Es], Dict0, Avps) ->
801    select(E, RC, RC, Es, Dict0, Avps);
802
803select(_, [], _, Avps) ->
804    Avps.
805
806%% select/6
807
808select(E, RC, T, _, Dict0, Avps)
809  when E, 3000 =< RC, RC < 4000;                 %% E-bit with 3xxx
810       E, ?BASE /= Dict0, 5000 =< RC, RC < 6000; %% E-bit with 5xxx
811       not E, RC < 3000 orelse 4000 =< RC ->     %% no E-bit
812    [T | Avps];
813
814select(E, _, _, Es, Dict0, Avps) ->
815    select(E, Es, Dict0, Avps).
816
817%% eval_packet/2
818
819eval_packet(Pkt, Fs) ->
820    lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs).
821
822%% make_answer_packet/4
823
824%% Use decode errors to set Result-Code and/or Failed-AVP unless the
825%% the errors field has been explicitly set. Unfortunately, the
826%% default value is the empty list rather than 'undefined' so use the
827%% atom 'false' for "set nothing". (This is historical and changing
828%% the default value would impact anyone expecting relying on the old
829%% default.)
830
831make_answer_packet(#diameter_packet{header = Hdr,
832                                    msg = Msg,
833                                    errors = Es,
834                                    transport_data = TD},
835                   #diameter_packet{header = Hdr0,
836                                    errors = Es0},
837                   MsgDict,
838                   Dict0) ->
839    #diameter_packet{header = make_answer_header(Hdr0, Hdr),
840                     msg = reset(Msg, Es0, Es, MsgDict, Dict0),
841                     transport_data = TD};
842
843%% Binaries and header/avp lists are sent as-is.
844make_answer_packet(Bin, #diameter_packet{transport_data = TD}, _, _)
845  when is_binary(Bin) ->
846    #diameter_packet{bin = Bin,
847                     transport_data = TD};
848make_answer_packet([#diameter_header{} | _] = Msg,
849                   #diameter_packet{transport_data = TD},
850                   _,
851                   _) ->
852    #diameter_packet{msg = Msg,
853                     transport_data = TD};
854
855make_answer_packet(Msg,
856                   #diameter_packet{header = Hdr,
857                                    errors = Es,
858                                    transport_data = TD},
859                   MsgDict,
860                   Dict0) ->
861    #diameter_packet{header = make_answer_header(Hdr, undefined),
862                     msg = reset(Msg, [], Es, MsgDict, Dict0),
863                     transport_data = TD}.
864
865%% make_answer_header/2
866
867%% A reply message clears the R and T flags and retains the P flag.
868%% The E flag will be set at encode. 6.2 of 3588 requires the same P
869%% flag on an answer as on the request. A #diameter_packet{} returned
870%% from a handle_request callback can circumvent this by setting its
871%% own header values.
872make_answer_header(ReqHdr, Hdr) ->
873    Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION,
874                                  is_request = false,
875                                  is_error = undefined,
876                                  is_retransmitted = false},
877    fold_record(Hdr0, Hdr).
878
879%% reset/5
880
881reset(Msg, [_|_] = Es0, [] = Es, MsgDict, Dict0) ->
882    reset(Msg, Es, Es0, MsgDict, Dict0);
883
884reset(Msg, _, Es, _, _)
885  when Es == false;
886       Es == [] ->
887    Msg;
888
889reset(Msg, _, Es, MsgDict, Dict0) ->
890    E = is_answer_message(Msg, Dict0),
891    reset(Msg, select_error(E, Es, Dict0), choose(E, Dict0, MsgDict)).
892
893%% reset/4
894%%
895%% Set Result-Code and/or Failed-AVP (maybe). Only RC and {RC, AVP}
896%% are the result of decode. AVP or {RC, [AVP]} can be set in an
897%% answer for encode, as a convenience for injecting additional AVPs
898%% into Failed-AVP; eg. 5001 = DIAMETER_AVP_UNSUPPORTED.
899
900reset(Msg, [], _) ->
901    Msg;
902
903reset(Msg, [{RC, As} | Avps], Dict)
904  when is_list(As) ->
905    reset(Msg, [RC | As ++ Avps], Dict);
906
907reset(Msg, [{RC, Avp} | Avps], Dict) ->
908    reset(Msg, [RC, Avp | Avps], Dict);
909
910reset(Msg, [#diameter_avp{} | _] = Avps, Dict) ->
911    set(Msg, failed_avp(Msg, Avps, Dict), Dict);
912
913reset(Msg, [RC | Avps], Dict) ->
914    set(Msg, rc(Msg, RC, Dict) ++ failed_avp(Msg, Avps, Dict), Dict).
915
916%% set/3
917
918%% Reply as name/values list ...
919set([Name|As], Avps, _)
920  when is_map(As) ->
921    [Name | maps:merge(As, maps:from_list(Avps))];
922set([_|_] = Ans, Avps, _) ->
923    Ans ++ Avps;  %% Values nearer tail take precedence.
924
925%% ... or record.
926set(Rec, Avps, Dict) ->
927    Dict:'#set-'(Avps, Rec).
928
929%% rc/3
930%%
931%% Turn the result code into a list if its optional and only set it if
932%% the arity is 1 or {0,1}. In other cases (which probably shouldn't
933%% exist in practice) we can't know what's appropriate.
934
935rc([MsgName | _], RC, Dict) ->
936    K = 'Result-Code',
937    case Dict:avp_arity(MsgName, K) of
938        1     -> [{K, RC}];
939        {0,1} -> [{K, [RC]}];
940        _     -> []
941    end;
942
943rc(Rec, RC, Dict) ->
944    rc([Dict:rec2msg(element(1, Rec))], RC, Dict).
945
946%% failed_avp/3
947
948failed_avp(_, [] = No, _) ->
949    No;
950
951failed_avp(Msg, [_|_] = Avps, Dict) ->
952    [failed(Msg, [{'AVP', Avps}], Dict)].
953
954%% failed/3
955
956failed(Msg, FailedAvp, Dict) ->
957    RecName = msg2rec(Msg, Dict),
958    try
959        Dict:'#info-'(RecName, {index, 'Failed-AVP'}), %% assert existence
960        {'Failed-AVP', [FailedAvp]}
961    catch
962        error: _ ->
963            Avps = values(Msg, 'AVP', Dict),
964            A = #diameter_avp{name = 'Failed-AVP',
965                              value = FailedAvp},
966            {'AVP', [A|Avps]}
967    end.
968
969%% msg2rec/2
970
971%% Message as name/values list ...
972msg2rec([MsgName | _], Dict) ->
973    Dict:msg2rec(MsgName);
974
975%% ... or record.
976msg2rec(Rec, _) ->
977    element(1, Rec).
978
979%% values/2
980
981%% Message as name/values list ...
982values([_ | Avps], F, _) ->
983    if is_map(Avps) ->
984            maps:get(F, Avps, []);
985       is_list(Avps) ->
986            proplists:get_value(F, Avps, [])
987    end;
988
989%% ... or record.
990values(Rec, F, Dict) ->
991    Dict:'#get-'(F, Rec).
992
993%% 3.  Diameter Header
994%%
995%%       E(rror)     - If set, the message contains a protocol error,
996%%                     and the message will not conform to the ABNF
997%%                     described for this command.  Messages with the 'E'
998%%                     bit set are commonly referred to as error
999%%                     messages.  This bit MUST NOT be set in request
1000%%                     messages.  See Section 7.2.
1001
1002%% 3.2.  Command Code ABNF specification
1003%%
1004%%    e-bit            = ", ERR"
1005%%                       ; If present, the 'E' bit in the Command
1006%%                       ; Flags is set, indicating that the answer
1007%%                       ; message contains a Result-Code AVP in
1008%%                       ; the "protocol error" class.
1009
1010%% 7.1.3.  Protocol Errors
1011%%
1012%%    Errors that fall within the Protocol Error category SHOULD be treated
1013%%    on a per-hop basis, and Diameter proxies MAY attempt to correct the
1014%%    error, if it is possible.  Note that these and only these errors MUST
1015%%    only be used in answer messages whose 'E' bit is set.
1016
1017%% Thus, only construct answers to protocol errors. Other errors
1018%% require an message-specific answer and must be handled by the
1019%% application.
1020
1021%% 6.2.  Diameter Answer Processing
1022%%
1023%%    When a request is locally processed, the following procedures MUST be
1024%%    applied to create the associated answer, in addition to any
1025%%    additional procedures that MAY be discussed in the Diameter
1026%%    application defining the command:
1027%%
1028%%    -  The same Hop-by-Hop identifier in the request is used in the
1029%%       answer.
1030%%
1031%%    -  The local host's identity is encoded in the Origin-Host AVP.
1032%%
1033%%    -  The Destination-Host and Destination-Realm AVPs MUST NOT be
1034%%       present in the answer message.
1035%%
1036%%    -  The Result-Code AVP is added with its value indicating success or
1037%%       failure.
1038%%
1039%%    -  If the Session-Id is present in the request, it MUST be included
1040%%       in the answer.
1041%%
1042%%    -  Any Proxy-Info AVPs in the request MUST be added to the answer
1043%%       message, in the same order they were present in the request.
1044%%
1045%%    -  The 'P' bit is set to the same value as the one in the request.
1046%%
1047%%    -  The same End-to-End identifier in the request is used in the
1048%%       answer.
1049%%
1050%%    Note that the error messages (see Section 7.3) are also subjected to
1051%%    the above processing rules.
1052
1053%% 7.3.  Error-Message AVP
1054%%
1055%%    The Error-Message AVP (AVP Code 281) is of type UTF8String.  It MAY
1056%%    accompany a Result-Code AVP as a human readable error message.  The
1057%%    Error-Message AVP is not intended to be useful in real-time, and
1058%%    SHOULD NOT be expected to be parsed by network entities.
1059
1060%% answer_message/4
1061
1062answer_message(RC,
1063               Dict0,
1064               #diameter_caps{origin_host  = {OH,_},
1065                              origin_realm = {OR,_}},
1066               #diameter_packet{avps = Avps,
1067                                errors = Es}) ->
1068    ['answer-message', {'Origin-Host', OH},
1069                       {'Origin-Realm', OR},
1070                       {'Result-Code', RC}
1071     | session_id(Dict0, Avps)
1072       ++ failed_avp(RC, Es)
1073       ++ proxy_info(Dict0, Avps)].
1074
1075session_id(Dict0, Avps) ->
1076    {Code, _, Vid} = Dict0:avp_header('Session-Id'),
1077    try
1078        #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
1079        [{'Session-Id', [Bin]}]
1080    catch
1081        error: _ ->
1082            []
1083    end.
1084
1085%% Note that this should only match 5xxx result codes currently but
1086%% don't bother distinguishing this case.
1087failed_avp(RC, [{RC, Avp} | _]) ->
1088    [{'Failed-AVP', [{'AVP', [Avp]}]}];
1089failed_avp(RC, [_ | Es]) ->
1090    failed_avp(RC, Es);
1091failed_avp(_, [] = No) ->
1092    No.
1093
1094proxy_info(Dict0, Avps) ->
1095    {Code, _, Vid} = Dict0:avp_header('Proxy-Info'),
1096    [{'AVP', [A#diameter_avp{value = undefined}
1097              || [#diameter_avp{code = C, vendor_id = I} = A | _]
1098                     <- Avps,
1099                 C == Code,
1100                 I == Vid]}].
1101
1102%% find_avp/3
1103
1104%% Grouped ...
1105find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As
1106                     | _]) ->
1107    As;
1108
1109%% ... or not.
1110find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) ->
1111    A;
1112
1113find_avp(Code, VId, [_ | Avps]) ->
1114    find_avp(Code, VId, Avps).
1115
1116%% 7.  Error Handling
1117%%
1118%%    There are certain Result-Code AVP application errors that require
1119%%    additional AVPs to be present in the answer.  In these cases, the
1120%%    Diameter node that sets the Result-Code AVP to indicate the error
1121%%    MUST add the AVPs.  Examples are:
1122%%
1123%%    -  An unrecognized AVP is received with the 'M' bit (Mandatory bit)
1124%%       set, causes an answer to be sent with the Result-Code AVP set to
1125%%       DIAMETER_AVP_UNSUPPORTED, and the Failed-AVP AVP containing the
1126%%       offending AVP.
1127%%
1128%%    -  An AVP that is received with an unrecognized value causes an
1129%%       answer to be returned with the Result-Code AVP set to
1130%%       DIAMETER_INVALID_AVP_VALUE, with the Failed-AVP AVP containing the
1131%%       AVP causing the error.
1132%%
1133%%    -  A command is received with an AVP that is omitted, yet is
1134%%       mandatory according to the command's ABNF.  The receiver issues an
1135%%       answer with the Result-Code set to DIAMETER_MISSING_AVP, and
1136%%       creates an AVP with the AVP Code and other fields set as expected
1137%%       in the missing AVP.  The created AVP is then added to the Failed-
1138%%       AVP AVP.
1139%%
1140%%    The Result-Code AVP describes the error that the Diameter node
1141%%    encountered in its processing.  In case there are multiple errors,
1142%%    the Diameter node MUST report only the first error it encountered
1143%%    (detected possibly in some implementation dependent order).  The
1144%%    specific errors that can be described by this AVP are described in
1145%%    the following section.
1146
1147%% 7.5.  Failed-AVP AVP
1148%%
1149%%    The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides
1150%%    debugging information in cases where a request is rejected or not
1151%%    fully processed due to erroneous information in a specific AVP.  The
1152%%    value of the Result-Code AVP will provide information on the reason
1153%%    for the Failed-AVP AVP.
1154%%
1155%%    The possible reasons for this AVP are the presence of an improperly
1156%%    constructed AVP, an unsupported or unrecognized AVP, an invalid AVP
1157%%    value, the omission of a required AVP, the presence of an explicitly
1158%%    excluded AVP (see tables in Section 10), or the presence of two or
1159%%    more occurrences of an AVP which is restricted to 0, 1, or 0-1
1160%%    occurrences.
1161%%
1162%%    A Diameter message MAY contain one Failed-AVP AVP, containing the
1163%%    entire AVP that could not be processed successfully.  If the failure
1164%%    reason is omission of a required AVP, an AVP with the missing AVP
1165%%    code, the missing vendor id, and a zero filled payload of the minimum
1166%%    required length for the omitted AVP will be added.
1167
1168%% incr_result/5
1169%%
1170%% Increment a stats counter for result codes in incoming and outgoing
1171%% answers.
1172
1173%% Message sent as a header/avps list.
1174incr_result(send = Dir,
1175            Avp,
1176            #diameter_packet{msg = [#diameter_header{} = H | _]},
1177            TPid,
1178            AppDict) ->
1179    incr_result(Dir, Avp, H, [], TPid, AppDict);
1180
1181%% Incoming or outgoing. Outgoing with encode errors never gets here
1182%% since encode fails.
1183incr_result(Dir, Avp, Pkt, TPid, AppDict) ->
1184    #diameter_packet{header = H, errors = Es}
1185        = Pkt,
1186    incr_result(Dir, Avp, H, Es, TPid, AppDict).
1187
1188%% incr_result/6
1189
1190incr_result(Dir, Avp, Hdr, Es, TPid, AppDict) ->
1191    Id = msg_id(Hdr, AppDict),
1192    %% Could be {relay, 0}, in which case the R-bit is redundant since
1193    %% only answers are being counted. Let it be however, so that the
1194    %% same tuple is in both send/recv and result code counters.
1195
1196    %% Count incoming decode errors.
1197    send == Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
1198
1199    Ctr = rcc(Avp),
1200    incr(TPid, {Id, Dir, Ctr}),
1201    Ctr.
1202
1203%% msg_id/2
1204
1205msg_id(#diameter_packet{header = H}, AppDict) ->
1206    msg_id(H, AppDict);
1207
1208%% Only count on known keys so as not to be vulnerable to attack:
1209%% there are 2^32 (application ids) * 2^24 (command codes) = 2^56
1210%% pairs for an attacker to choose from.
1211msg_id(Hdr, AppDict) ->
1212    {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr),
1213    case AppDict:id() of
1214        ?APP_ID_RELAY ->
1215            {relay, R};
1216        A ->
1217            unknown(A /= Aid orelse '' == AppDict:msg_name(Code, 0 == R), Id)
1218    end.
1219
1220unknown(true, {_, _, R}) ->
1221    {unknown, R};
1222unknown(false, Id) ->
1223    Id.
1224
1225%% No E-bit: can't be 3xxx.
1226is_result(RC, false, _Dict0) ->
1227    RC < 3000 orelse 4000 =< RC;
1228
1229%% E-bit in RFC 3588: only 3xxx.
1230is_result(RC, true, ?BASE) ->
1231    3000 =< RC andalso RC < 4000;
1232
1233%% E-bit in RFC 6733: 3xxx or 5xxx.
1234is_result(RC, true, _) ->
1235    3000 =< RC andalso RC < 4000
1236        orelse
1237        5000 =< RC andalso RC < 6000.
1238
1239%% incr/2
1240
1241incr(TPid, Counter) ->
1242    diameter_stats:incr(Counter, TPid, 1).
1243
1244%% rcc/1
1245
1246rcc(#diameter_avp{name = 'Result-Code' = Name, value = V}) ->
1247    {Name, head(V)};
1248
1249rcc(#diameter_avp{name = 'Experimental-Result', value = V}) ->
1250    head(V).
1251
1252%% head/1
1253
1254head([V|_]) ->
1255    V;
1256head(V) ->
1257    V.
1258
1259%% rcv/1
1260
1261rcv(#diameter_avp{name = N, value = V}) ->
1262    rcv(N, head(V)).
1263
1264%% rcv/2
1265
1266rcv('Experimental-Result', {_,_,N}) ->
1267    N;
1268
1269rcv('Result-Code', N) ->
1270    N.
1271
1272%% get_result/4
1273
1274%% Message sent as binary: no checks or counting.
1275get_result(_, _, _, #diameter_packet{header = undefined}) ->
1276    false;
1277
1278get_result(Dir, MsgDict, Dict0, Pkt) ->
1279    Avp = get_result(MsgDict, msg(Dir, Pkt)),
1280    Hdr = Pkt#diameter_packet.header,
1281    %% Exit on a missing result code or inappropriate value.
1282    Avp == false
1283        andalso ?LOGX(no_result_code, {MsgDict, Dir, Hdr}),
1284    E = Hdr#diameter_header.is_error,
1285    is_result(rcv(Avp), E, Dict0)
1286        orelse ?LOGX(invalid_error_bit, {MsgDict, Dir, Hdr, Avp}),
1287    Avp.
1288
1289%% RFC 3588, 7.6:
1290%%
1291%%   All Diameter answer messages defined in vendor-specific
1292%%   applications MUST include either one Result-Code AVP or one
1293%%   Experimental-Result AVP.
1294
1295%% msg/2
1296
1297msg(Dir, #diameter_packet{header = H,
1298                          avps = As,
1299                          msg = Msg})
1300  when Dir == recv;         %% decoded incoming
1301       Msg == undefined ->  %% relayed outgoing
1302    [H|As];
1303
1304msg(_, #diameter_packet{msg = Msg}) ->
1305    Msg.
1306
1307%% get_result/2
1308
1309get_result(Dict, Msg) ->
1310    try
1311        [throw(A) || N <- ['Result-Code', 'Experimental-Result'],
1312                     #diameter_avp{} = A <- [get_avp(Dict, N, Msg)],
1313                     is_integer(catch rcv(A))],
1314        false
1315    catch
1316        #diameter_avp{} = A ->
1317            A
1318    end.
1319
1320x(T) ->
1321    exit(T).
1322
1323%% ---------------------------------------------------------------------------
1324%% send_request/4
1325%%
1326%% Handle an outgoing Diameter request.
1327%% ---------------------------------------------------------------------------
1328
1329send_request(SvcName, AppOrAlias, Msg, Options)
1330  when is_list(Options) ->
1331    Rec = make_options(Options),
1332    Ref = make_ref(),
1333    Caller = {self(), Ref},
1334    ReqF = fun() ->
1335                   exit({Ref, send_R(SvcName, AppOrAlias, Msg, Rec, Caller)})
1336           end,
1337    try spawn_monitor(ReqF) of
1338        {_, MRef} ->
1339            recv_A(MRef, Ref, Rec#options.detach, false)
1340    catch
1341        error: system_limit = E ->
1342            {error, E}
1343    end.
1344%% The R in send_R is because Diameter request are usually given short
1345%% names of the form XXR. (eg. CER, DWR, etc.) Similarly, answers have
1346%% names of the form XXA.
1347
1348%% Don't rely on gen_server:call/3 for the timeout handling since it
1349%% makes no guarantees about not leaving a reply message in the
1350%% mailbox if we catch its exit at timeout. It currently *can* do so,
1351%% which is also undocumented.
1352
1353recv_A(MRef, _, true, true) ->
1354    erlang:demonitor(MRef, [flush]),
1355    ok;
1356
1357recv_A(MRef, Ref, Detach, Sent) ->
1358    receive
1359        Ref ->  %% send has been attempted
1360            recv_A(MRef, Ref, Detach, true);
1361        {'DOWN', MRef, process, _, Reason} ->
1362            answer_rc(Reason, Ref, Sent)
1363    end.
1364
1365%% send_R/5 has returned ...
1366answer_rc({Ref, Ans}, Ref, _) ->
1367    Ans;
1368
1369%% ... or not. Note that failure/encode are documented return values.
1370answer_rc(_, _, Sent) ->
1371    {error, choose(Sent, failure, encode)}.
1372
1373%% send_R/5
1374%%
1375%% In the process spawned for the outgoing request.
1376
1377send_R(SvcName, AppOrAlias, Msg, CallOpts, Caller) ->
1378    case pick_peer(SvcName, AppOrAlias, Msg, CallOpts) of
1379        {{_,_} = Transport, SvcOpts} ->
1380            send_request(Transport, SvcOpts, Msg, CallOpts, Caller, SvcName);
1381        {error, _} = No ->
1382            No
1383    end.
1384
1385%% make_options/1
1386
1387make_options(Options) ->
1388    make_opts(Options, [], false, [], none, 5000).
1389
1390%% Do our own recursion since this is faster than a lists:foldl/3
1391%% setting elements in an #options{} accumulator.
1392
1393make_opts([], Peers, Detach, Extra, Filter, Tmo) ->
1394    #options{peers = lists:reverse(Peers),
1395             detach = Detach,
1396             extra = Extra,
1397             filter = Filter,
1398             timeout = Tmo};
1399
1400make_opts([{timeout, Tmo} | Rest], Peers, Detach, Extra, Filter, _)
1401  when is_integer(Tmo), 0 =< Tmo ->
1402    make_opts(Rest, Peers, Detach, Extra, Filter, Tmo);
1403
1404make_opts([{filter, F} | Rest], Peers, Detach, Extra, none, Tmo) ->
1405    make_opts(Rest, Peers, Detach, Extra, F, Tmo);
1406make_opts([{filter, F} | Rest], Peers, Detach, Extra, {all, Fs}, Tmo) ->
1407    make_opts(Rest, Peers, Detach, Extra, {all, [F|Fs]}, Tmo);
1408make_opts([{filter, F} | Rest], Peers, Detach, Extra, F0, Tmo) ->
1409    make_opts(Rest, Peers, Detach, Extra, {all, [F0, F]}, Tmo);
1410
1411make_opts([{extra, L} | Rest], Peers, Detach, Extra, Filter, Tmo)
1412  when is_list(L) ->
1413    make_opts(Rest, Peers, Detach, Extra ++ L, Filter, Tmo);
1414
1415make_opts([detach | Rest], Peers, _, Extra, Filter, Tmo) ->
1416    make_opts(Rest, Peers, true, Extra, Filter, Tmo);
1417
1418make_opts([{peer, TPid} | Rest], Peers, Detach, Extra, Filter, Tmo)
1419  when is_pid(TPid) ->
1420    make_opts(Rest, [TPid | Peers], Detach, Extra, Filter, Tmo);
1421
1422make_opts([T | _], _, _, _, _, _) ->
1423    ?ERROR({invalid_option, T}).
1424
1425%% ---------------------------------------------------------------------------
1426%% send_request/6
1427%% ---------------------------------------------------------------------------
1428
1429%% Send an outgoing request in its dedicated process.
1430%%
1431%% Note that both encode of the outgoing request and of the received
1432%% answer happens in this process. It's also this process that replies
1433%% to the caller. The service process only handles the state-retaining
1434%% callbacks.
1435%%
1436%% The module field of the #diameter_app{} here includes any extra
1437%% arguments passed to diameter:call/4.
1438
1439send_request({{TPid, _Caps} = TC, App}
1440             = Transport,
1441             #{sequence := Mask, traffic_counters := Count}
1442             = SvcOpts,
1443             Msg0,
1444             CallOpts,
1445             Caller,
1446             SvcName) ->
1447    Pkt = make_prepare_packet(Mask, Msg0),
1448
1449    case prepare(cb(App, prepare_request, [Pkt, SvcName, TC]), []) of
1450        [Msg | Fs] ->
1451            ReqPkt = make_request_packet(Msg, Pkt),
1452            EncPkt = encode(App#diameter_app.dictionary,
1453                            TPid,
1454                            SvcOpts,
1455                            ReqPkt),
1456            eval_packet(EncPkt, Fs),
1457            T = send_R(ReqPkt,
1458                       EncPkt,
1459                       Transport,
1460                       CallOpts,
1461                       Caller,
1462                       Count,
1463                       SvcName),
1464            Ans = recv_answer(SvcName, App, CallOpts, T),
1465            handle_answer(SvcName, Count, SvcOpts, App, Ans);
1466        {discard, Reason} ->
1467            {error, Reason};
1468        discard ->
1469            {error, discarded};
1470        {error, Reason} ->
1471            ?ERROR({invalid_return, Reason, prepare_request, App})
1472    end.
1473
1474%% prepare/2
1475
1476prepare({send, Msg}, Fs) ->
1477    [Msg | Fs];
1478
1479prepare({eval_packet, RC, F}, Fs) ->
1480    prepare(RC, [F|Fs]);
1481
1482prepare({discard, _Reason} = RC, _) ->
1483    RC;
1484
1485prepare(discard = RC, _) ->
1486    RC;
1487
1488prepare(Reason, _) ->
1489    {error, Reason}.
1490
1491%% make_prepare_packet/2
1492%%
1493%% Turn an outgoing request as passed to call/4 into a diameter_packet
1494%% record in preparation for a prepare_request callback.
1495
1496make_prepare_packet(_, Bin)
1497  when is_binary(Bin) ->
1498    #diameter_packet{header = diameter_codec:decode_header(Bin),
1499                     bin = Bin};
1500
1501make_prepare_packet(Mask, #diameter_packet{msg = [#diameter_header{} = Hdr
1502                                                  | Avps]}
1503                          = Pkt) ->
1504    Pkt#diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]};
1505
1506make_prepare_packet(Mask, #diameter_packet{header = Hdr} = Pkt) ->
1507    Pkt#diameter_packet{header = make_prepare_header(Mask, Hdr)};
1508
1509make_prepare_packet(Mask, [#diameter_header{} = Hdr | Avps]) ->
1510    #diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]};
1511
1512make_prepare_packet(Mask, Msg) ->
1513    #diameter_packet{header = make_prepare_header(Mask, undefined),
1514                     msg = Msg}.
1515
1516%% make_prepare_header/2
1517
1518make_prepare_header(Mask, undefined) ->
1519    Seq = diameter_session:sequence(Mask),
1520    #diameter_header{version = ?DIAMETER_VERSION,
1521                     end_to_end_id = Seq,
1522                     hop_by_hop_id = Seq};
1523
1524make_prepare_header(Mask, #diameter_header{version = V,
1525                                           end_to_end_id = EI,
1526                                           hop_by_hop_id = HI}
1527                          = H)
1528  when EI == undefined;
1529       HI == undefined ->
1530    Id = diameter_session:sequence(Mask),
1531    H#diameter_header{version = ?DEFAULT(V, ?DIAMETER_VERSION),
1532                      end_to_end_id = ?DEFAULT(EI, Id),
1533                      hop_by_hop_id = ?DEFAULT(HI, Id)};
1534
1535make_prepare_header(_, #diameter_header{version = undefined} = H) ->
1536    H#diameter_header{version = ?DIAMETER_VERSION};
1537
1538make_prepare_header(_, #diameter_header{} = H) ->
1539    H;
1540
1541make_prepare_header(_, T) ->
1542    ?ERROR({invalid_header, T}).
1543
1544%% make_request_packet/2
1545%%
1546%% Reconstruct a diameter_packet from the return value of
1547%% prepare_request or prepare_retransmit callback.
1548
1549make_request_packet(Bin, _)
1550  when is_binary(Bin) ->
1551    make_prepare_packet(false, Bin);
1552
1553make_request_packet(#diameter_packet{msg = [#diameter_header{} | _]}
1554                    = Pkt,
1555                    _) ->
1556    Pkt;
1557
1558%% Returning a diameter_packet with no header from a prepare_request
1559%% or prepare_retransmit callback retains the header passed into it.
1560%% This is primarily so that the end to end and hop by hop identifiers
1561%% are retained.
1562make_request_packet(#diameter_packet{header = Hdr} = Pkt,
1563                    #diameter_packet{header = Hdr0}) ->
1564    Pkt#diameter_packet{header = fold_record(Hdr0, Hdr)};
1565
1566make_request_packet(Msg, Pkt) ->
1567    Pkt#diameter_packet{msg = Msg}.
1568
1569%% make_retransmit_packet/1
1570
1571make_retransmit_packet(#diameter_packet{msg = [#diameter_header{} = Hdr
1572                                               | Avps]}
1573                       = Pkt) ->
1574    Pkt#diameter_packet{msg = [make_retransmit_header(Hdr) | Avps]};
1575
1576make_retransmit_packet(#diameter_packet{header = Hdr} = Pkt) ->
1577    Pkt#diameter_packet{header = make_retransmit_header(Hdr)}.
1578
1579%% make_retransmit_header/1
1580
1581make_retransmit_header(Hdr) ->
1582    Hdr#diameter_header{is_retransmitted = true}.
1583
1584%% fold_record/2
1585%%
1586%% Replace elements in the first record by those in the second that
1587%% differ from undefined.
1588
1589fold_record(Rec0, undefined) ->
1590    Rec0;
1591fold_record(Rec0, Rec) ->
1592    list_to_tuple(fold(tuple_to_list(Rec0), tuple_to_list(Rec))).
1593
1594fold([], []) ->
1595    [];
1596fold([H | T0], [undefined | T]) ->
1597    [H | fold(T0, T)];
1598fold([_ | T0], [H | T]) ->
1599    [H | fold(T0, T)].
1600
1601%% send_R/6
1602
1603send_R(ReqPkt,
1604       EncPkt,
1605       {{TPid, _Caps} = TC, #diameter_app{dictionary = AppDict}},
1606       #options{timeout = Timeout},
1607       {Pid, Ref},
1608       Count,
1609       SvcName) ->
1610    Req = #request{ref = Ref,
1611                   caller = Pid,
1612                   handler = self(),
1613                   peer = TC,
1614                   packet = ReqPkt},
1615
1616    Count andalso incr(send, EncPkt, TPid, AppDict),
1617    {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Timeout),
1618    Pid ! Ref,  %% tell caller a send has been attempted
1619    {TRef, MRef, Req}.
1620
1621%% recv_answer/4
1622
1623recv_answer(SvcName, App, CallOpts, {TRef, MRef, #request{ref = Ref}
1624                                                 = Req}) ->
1625    %% Matching on TRef below ensures we ignore messages that pertain
1626    %% to a previous transport prior to failover. The answer message
1627    %% includes the pid of the transport on which it was received,
1628    %% which may not be the last peer to which we've transmitted.
1629    receive
1630        {answer = A, Ref, TPid, Dict0, Pkt} ->  %% Answer from peer
1631            {A, #request{} = erase(TPid), Dict0, Pkt};
1632        {timeout = Reason, TRef, _} ->        %% No timely reply
1633            {error, Req, Reason};
1634        {'DOWN', MRef, process, _, _} when false /= MRef -> %% local peer_down
1635            failover(SvcName, App, Req, CallOpts);
1636        {failover, TRef} ->                   %% local or remote peer_down
1637            failover(SvcName, App, Req, CallOpts)
1638    end.
1639
1640%% failover/4
1641
1642failover(SvcName, App, Req, CallOpts) ->
1643    resend_request(pick_peer(SvcName, App, Req, CallOpts),
1644                   Req,
1645                   CallOpts,
1646                   SvcName).
1647
1648%% handle_answer/5
1649
1650handle_answer(SvcName, _, _, App, {error, Req, Reason}) ->
1651    #request{packet = Pkt,
1652             peer = {_TPid, _Caps} = TC}
1653        = Req,
1654    cb(App, handle_error, [Reason, msg(Pkt), SvcName, TC]);
1655
1656handle_answer(SvcName,
1657              Count,
1658              SvcOpts,
1659              #diameter_app{id = Id,
1660                            dictionary = AppDict,
1661                            options = [{answer_errors, AE} | _]}
1662              = App,
1663              {answer, Req, Dict0, Pkt}) ->
1664    MsgDict = msg_dict(AppDict, Dict0, Pkt),
1665    DecPkt = errors(Id, diameter_codec:decode({MsgDict, AppDict},
1666                                              SvcOpts,
1667                                              Pkt)),
1668    #request{peer = {TPid, _}}
1669        = Req,
1670
1671    answer(answer(DecPkt, TPid, MsgDict, AppDict, Dict0, Count),
1672           SvcName,
1673           App,
1674           AE,
1675           Req).
1676
1677%% answer/6
1678
1679answer(DecPkt, TPid, MsgDict, AppDict, Dict0, Count) ->
1680    Count andalso incr(recv, DecPkt, TPid, AppDict),
1681    try get_result(recv, MsgDict, Dict0, DecPkt) of
1682        Avp ->
1683            Count andalso false /= Avp
1684                  andalso incr_result(recv, Avp, DecPkt, TPid, AppDict),
1685            DecPkt
1686    catch
1687        exit: {no_result_code, _} ->
1688            %% RFC 6733 requires one of Result-Code or
1689            %% Experimental-Result, but the decode will have
1690            %% detected a missing AVP. If both are optional in
1691            %% the dictionary then this isn't a decode error:
1692            %% just continue on.
1693            DecPkt;
1694        exit: {invalid_error_bit, {_, _, _, Avp}} ->
1695            #diameter_packet{errors = Es}
1696                = DecPkt,
1697            E = {5004, Avp},
1698            DecPkt#diameter_packet{errors = [E|Es]}
1699    end.
1700
1701%% answer/5
1702
1703answer(#diameter_packet{errors = Es}
1704       = Pkt,
1705       SvcName,
1706       App,
1707       AE,
1708       #request{peer = {_TPid, _Caps} = TC,
1709                packet = P})
1710  when callback == AE;
1711       [] == Es ->
1712    cb(App, handle_answer, [Pkt, msg(P), SvcName, TC]);
1713
1714answer(#diameter_packet{header = H}, SvcName, _, AE, _) ->
1715    handle_error(H, SvcName, AE).
1716
1717%% handle_error/3
1718
1719-spec handle_error(_, _, _) -> no_return().  %% silence dialyzer
1720
1721handle_error(Hdr, SvcName, report) ->
1722    MFA = {?MODULE, handle_answer, [SvcName, Hdr]},
1723    diameter_lib:warning_report(errors, MFA),
1724    handle_error(Hdr, SvcName, discard);
1725
1726handle_error(Hdr, SvcName, discard) ->
1727    x({answer_errors, {SvcName, Hdr}}).
1728
1729%% Note that we don't check that the application id in the answer's
1730%% header is what we expect. (TODO: Does the rfc says anything about
1731%% this?)
1732
1733%% Note that failover starts a new timer and that expiry of an old
1734%% timer value is ignored. This means that an answer could be accepted
1735%% from a peer after timeout in the case of failover.
1736
1737%% resend_request/4
1738
1739resend_request({{{TPid, _Caps} = TC, App}, SvcOpts},
1740               Req0,
1741               #options{timeout = Timeout}
1742               = CallOpts,
1743               SvcName) ->
1744    case
1745        undefined == get(TPid)
1746        andalso prepare_retransmit(TC, App, Req0, SvcName)
1747    of
1748        [ReqPkt | Fs] ->
1749            AppDict = App#diameter_app.dictionary,
1750            EncPkt = encode(AppDict, TPid, SvcOpts, ReqPkt),
1751            eval_packet(EncPkt, Fs),
1752            Req = Req0#request{peer = TC,
1753                               packet = ReqPkt},
1754            ?LOG(retransmission, EncPkt#diameter_packet.header),
1755            incr(TPid, {msg_id(EncPkt, AppDict), send, retransmission}),
1756            {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Timeout),
1757            recv_answer(SvcName, App, CallOpts, {TRef, MRef, Req});
1758        false ->
1759            {error, Req0, timeout};
1760        {discard, Reason} ->
1761            {error, Req0, Reason};
1762        discard ->
1763            {error, Req0, discarded};
1764        {error, T} ->
1765            ?ERROR({invalid_return, T, prepare_retransmit, App})
1766    end;
1767
1768resend_request(_, Req, _, _) ->  %% no alternate peer
1769    {error, Req, failover}.
1770
1771%% pick_peer/4
1772
1773%% Retransmission after failover: call-specific arguments have already
1774%% been appended in App.
1775pick_peer(SvcName,
1776          App,
1777          #request{packet = #diameter_packet{msg = Msg}},
1778          CallOpts) ->
1779    pick_peer(SvcName, App, Msg, CallOpts#options{extra = []});
1780
1781pick_peer(_, _, undefined, _) ->
1782    {error, no_connection};
1783
1784pick_peer(SvcName,
1785          AppOrAlias,
1786          Msg,
1787          #options{peers = TPids, filter = Filter, extra = Xtra}) ->
1788    X = {fun(D) -> get_destination(D, Msg) end, Filter, Xtra, TPids},
1789    case diameter_service:pick_peer(SvcName, AppOrAlias, X) of
1790        false ->
1791            {error, no_connection};
1792        T ->
1793            T
1794    end.
1795
1796msg(#diameter_packet{msg = undefined, bin = Bin}) ->
1797    Bin;
1798msg(#diameter_packet{msg = Msg}) ->
1799    Msg.
1800
1801%% encode/4
1802
1803%% Note that prepare_request can return a diameter_packet containing a
1804%% header or transport_data. Even allow the returned record to contain
1805%% an encoded binary. This isn't the usual case and doesn't properly
1806%% support retransmission but is useful for test.
1807
1808encode(Dict, TPid, Opts, Pkt)
1809  when is_atom(Dict) ->
1810    encode({Dict, Dict}, TPid, Opts, Pkt);
1811
1812%% A message to be encoded.
1813encode(DictT, TPid, Opts, #diameter_packet{bin = undefined} = Pkt) ->
1814    {Dict, AppDict} = DictT,
1815    try
1816        diameter_codec:encode(Dict, Opts, Pkt)
1817    catch
1818        exit: {diameter_codec, encode, T} = Reason ->
1819            incr_error(send, T, TPid, AppDict),
1820            exit(Reason)
1821    end;
1822
1823%% An encoded binary: just send.
1824encode(_, _, _, #diameter_packet{} = Pkt) ->
1825    Pkt.
1826
1827%% zend_requezt/5
1828%%
1829%% Strip potentially large record fields that aren't used by the
1830%% processes the records can be send to, possibly on a remote node.
1831
1832zend_requezt(TPid, Pkt, Req, SvcName, Timeout) ->
1833    put(TPid, Req),
1834    send_request(TPid, z(Pkt), Req, SvcName, Timeout).
1835
1836%% send_request/5
1837
1838send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, _SvcName, Timeout)
1839  when node() == node(TPid) ->
1840    Seqs = diameter_codec:sequence_numbers(Bin),
1841    TRef = erlang:start_timer(Timeout, self(), TPid),
1842    send(TPid, Pkt, _Route = {self(), Req#request.ref, Seqs}),
1843    {TRef, _MRef = peer_monitor(TPid, TRef)};
1844
1845%% Send using a remote transport: spawn a process on the remote node
1846%% to relay the answer.
1847send_request(TPid, #diameter_packet{} = Pkt, Req, SvcName, Timeout) ->
1848    TRef = erlang:start_timer(Timeout, self(), TPid),
1849    T = {TPid, Pkt, z(Req), SvcName, Timeout, TRef},
1850    spawn(node(TPid), ?MODULE, send, [T]),
1851    {TRef, false}.
1852
1853%% z/1
1854%%
1855%% Avoid sending potentially large terms unnecessarily. The records
1856%% themselves are retained since they're sent between nodes in send/1
1857%% and changing what's sent causes upgrade issues.
1858
1859z(#request{ref = Ref, handler = Pid}) ->
1860    #request{ref = Ref,
1861             handler = Pid};
1862
1863z(#diameter_packet{header = H, bin = Bin, transport_data = T}) ->
1864    #diameter_packet{header = H,
1865                     bin = Bin,
1866                     transport_data = T}.
1867
1868%% send/1
1869
1870send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) ->
1871    Req = Req0#request{handler = self()},
1872    recv(TPid, Pid, TRef, zend_requezt(TPid, Pkt, Req, SvcName, Timeout)).
1873
1874%% recv/4
1875%%
1876%% Relay an answer from a remote node.
1877
1878recv(TPid, Pid, TRef, {LocalTRef, MRef}) ->
1879    receive
1880        {answer, _, _, _, _} = A ->
1881            Pid ! A;
1882        {'DOWN', MRef, process, _, _} ->
1883            Pid ! {failover, TRef};
1884        {failover = T, LocalTRef} ->
1885            Pid ! {T, TRef};
1886        T ->
1887            exit({timeout, LocalTRef, TPid} = T)
1888    end.
1889
1890%% send/3
1891
1892send(Pid, Pkt, Route) ->
1893    Pid ! {send, Pkt, Route}.
1894
1895%% prepare_retransmit/4
1896
1897prepare_retransmit({_TPid, _Caps} = TC, App, Req, SvcName) ->
1898    Pkt = make_retransmit_packet(Req#request.packet),
1899
1900    case prepare(cb(App, prepare_retransmit, [Pkt, SvcName, TC]), []) of
1901        [Msg | Fs] ->
1902            [make_request_packet(Msg, Pkt) | Fs];
1903        No ->
1904            No
1905    end.
1906
1907%% When sending a binary, it's up to prepare_retransmit to modify it
1908%% accordingly.
1909
1910%% peer_monitor/2
1911
1912peer_monitor(TPid, TRef) ->
1913    case ets:lookup(?REQUEST_TABLE, TPid) of %% at peer_up/1
1914        [{_, MPid}] ->
1915            monitor(process, MPid);
1916        [] ->  %% transport has gone down
1917            self() ! {failover, TRef},
1918            false
1919    end.
1920
1921%% get_destination/2
1922
1923get_destination(Dict, Msg) ->
1924    [str(get_avp_value(Dict, D, Msg)) || D <- ['Destination-Realm',
1925                                               'Destination-Host']].
1926
1927%% A DiameterIdentity has length at least one, so an empty list is not
1928%% a Realm/Host.
1929str([]) ->
1930    undefined;
1931str(T) ->
1932    T.
1933
1934%% get_avp/3
1935%%
1936%% Find an AVP in a message in one of the decoded formats, or as a
1937%% header/avps list. There are only four AVPs that are extracted here:
1938%% Result-Code and Experimental-Result in order when constructing
1939%% counter keys, and Destination-Host/Realm when selecting a next-hop
1940%% peer. Experimental-Result is the only of type Grouped, and is given
1941%% special treatment in order to return the value as a record.
1942
1943%% Messages will be header/avps list as a relay and the only AVP's we
1944%% look for are in the common dictionary. This is required since the
1945%% relay dictionary doesn't inherit the common dictionary (which maybe
1946%% it should).
1947get_avp(?RELAY, Name, Msg) ->
1948    get_avp(?BASE, Name, Msg);
1949
1950%% Message as header/avps list.
1951get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
1952    try
1953        {Code, _, Vid} = Dict:avp_header(Name),
1954        A = find_avp(Code, Vid, Avps),
1955        avp_decode(Dict, Name, ungroup(A))
1956    catch
1957        {diameter_gen, _} ->  %% faulty Grouped AVP
1958            undefined;
1959        error: _ ->
1960            undefined
1961    end;
1962
1963%% Message as name/values list ...
1964get_avp(_, Name, [_MsgName | Avps]) ->
1965    case find(Name, Avps) of
1966        {_, V} ->
1967            #diameter_avp{name = Name, value = value(Name, V)};
1968        _ ->
1969            undefined
1970    end;
1971
1972%% ... or record.
1973get_avp(Dict, Name, Rec) ->
1974    try Dict:'#get-'(Name, Rec) of
1975        V ->
1976            #diameter_avp{name = Name, value = value(Name, V)}
1977    catch
1978        error:_ ->
1979            undefined
1980    end.
1981
1982value('Experimental-Result' = N, #{'Vendor-Id' := Vid,
1983                                   'Experimental-Result-Code' := RC}) ->
1984    {N, Vid, RC};
1985value('Experimental-Result' = N, [{'Experimental-Result-Code', RC},
1986                                  {'Vendor-Id', Vid}]) ->
1987    {N, Vid, RC};
1988value('Experimental-Result' = N, [{'Vendor-Id', Vid},
1989                                  {'Experimental-Result-Code', RC}]) ->
1990    {N, Vid, RC};
1991value(_, V) ->
1992    V.
1993
1994%% find/2
1995
1996find(Key, Map)
1997  when is_map(Map) ->
1998    maps:find(Key, Map);
1999
2000find(Key, List)
2001  when is_list(List) ->
2002    lists:keyfind(Key, 1, List).
2003
2004%% get_avp_value/3
2005
2006get_avp_value(Dict, Name, Msg) ->
2007    case get_avp(Dict, Name, Msg) of
2008        #diameter_avp{value = V} ->
2009            V;
2010        undefined = No ->
2011            No
2012    end.
2013
2014%% ungroup/1
2015
2016ungroup([Avp|_]) ->
2017    Avp;
2018ungroup(Avp) ->
2019    Avp.
2020
2021%% avp_decode/3
2022
2023%% Ensure Experimental-Result is decoded as record, since this format
2024%% is used for counter keys.
2025avp_decode(Dict, 'Experimental-Result' = N, #diameter_avp{data = Bin}
2026                                            = Avp)
2027  when is_binary(Bin) ->
2028    {V,_} = Dict:avp(decode, Bin, N, decode_opts(Dict)),
2029    Avp#diameter_avp{name = N, value = V};
2030
2031avp_decode(Dict, Name, #diameter_avp{value = undefined,
2032                                     data = Bin}
2033                       = Avp)
2034  when is_binary(Bin) ->
2035    V = Dict:avp(decode, Bin, Name, decode_opts(Dict)),
2036    Avp#diameter_avp{name = Name, value = V};
2037
2038avp_decode(_, Name, #diameter_avp{} = Avp) ->
2039    Avp#diameter_avp{name = Name}.
2040
2041%% cb/3
2042
2043cb(#diameter_app{module = [_|_] = M}, F, A) ->
2044    eval(M, F, A).
2045
2046eval([M|X], F, A) ->
2047    apply(M, F, A ++ X).
2048
2049choose(true, X, _)  -> X;
2050choose(false, _, X) -> X.
2051
2052%% Decode options sufficient for AVP extraction.
2053decode_opts(Dict) ->
2054    #{decode_format => record,
2055      string_decode => false,
2056      strict_mbit => false,
2057      failed_avp => false,
2058      module => Dict,
2059      app_dictionary => Dict}.
2060