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