diameter_app
Callback module of a Diameter application.
A diameter service as started by diameter:start_service/2 configures one of more Diameter applications, each of whose configuration specifies a callback that handles messages specific to the application. The messages and AVPs of the application are defined in a dictionary file whose format is documented in diameter_dict(4) while the callback module is documented here. The callback module implements the Diameter application-specific functionality of a service.
A callback module must export all of the functions documented below. The functions themselves are of three distinct flavours:
-
peer_up/3 and peer_up/3 signal the attainment or loss of connectivity with a Diameter peer.
-
pick_peer/4, prepare_request/3, prepare_retransmit/3, handle_answer/4 and handle_error/4 are (or may be) called as a consequence of a call to diameter:call/4 to send an outgoing Diameter request message.
-
handle_request/3 is called in response to an incoming Diameter request message.
Note!
The arities given for the the callback functions here assume no extra arguments. All functions will also be passed any extra arguments configured with the callback module itself when calling diameter:start_service/2 and, for the call-specific callbacks, any extra arguments passed to diameter:call/4.
DATA TYPES
capabilities() = #diameter_caps{}
A record containing the identities of the local Diameter node and the remote Diameter peer having an established transport connection, as well as the capabilities as determined by capabilities exchange. Each field of the record is a 2-tuple consisting of values for the (local) host and (remote) peer. Optional or possibly multiple values are encoded as lists of values, mandatory values as the bare value.
message() = diameter_codec:message()
The representation of a Diameter message as passed to diameter:call/4 or returned from a handle_request/3 callback.
packet() = diameter_codec:packet()
A container for incoming and outgoing Diameter messages that's passed through encode/decode and transport. Fields should not be set in return values except as documented.
peer_ref() = term()
A term identifying a transport connection with a Diameter peer.
peer() = {peer_ref(), capabilities()}
A tuple representing a Diameter peer connection.
state() = term()
The state maintained by the application callback functions peer_up/3, peer_up/3 and (optionally) pick_peer/4. The initial state is configured in the call to diameter:start_service/2 that configures the application on a service. Callback functions returning a state are evaluated in a common service-specific process while those not returning state are evaluated in a request-specific process.
Functions
Mod:peer_up(SvcName, Peer, State) -> NewState
SvcName = diameter:service_name()
Peer = peer()
State = NewState = state()
Invoked to signal the availability of a peer connection on the local
Erlang node.
In particular, capabilities exchange with the peer has indicated
support for the application in question, the RFC 3539 watchdog state
machine for the connection has reached state OKAY
and Diameter
messages can be both sent and received.
Note!
A watchdog state machine can reach state OKAY
from state
SUSPECT
without a new capabilities exchange taking place.
A new transport connection (and capabilities exchange) results in a
new peer_ref().
Note!
There is no requirement that a callback return before incoming requests are received: handle_request/3 callbacks must be handled independently of peer_up/3 and peer_up/3.
Mod:peer_down(SvcName, Peer, State) -> NewState
SvcName = diameter:service_name()
Peer = peer()
State = NewState = state()
Invoked to signal that a peer connection on the local Erlang node is
no longer available following a previous call to peer_up/3.
In particular, that the RFC 3539 watchdog state machine for the
connection has left state OKAY
and the peer will no longer be a
candidate in pick_peer/4 callbacks.
Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State) -> Selection | false
LocalCandidates = RemoteCandidates = [peer()]
SvcName = diameter:service_name()
State = NewState = state()
Selection = {ok, Peer} | {Peer, NewState}
Peer = peer() | false
Invoked as a consequence of a call to diameter:call/4 to select a destination peer for an outgoing request. The return value indicates the selected peer.
The candidate lists contain only those peers that have advertised
support for the Diameter application in question during capabilities
exchange, that have not be excluded by a filter
option in
the call to diameter:call/4
and whose watchdog state machine is in the OKAY
state.
The order of the elements is unspecified except that any
peers whose Origin-Host and Origin-Realm matches that of the
outgoing request (in the sense of a {filter, {all, [host, realm]}}
option to diameter:call/4)
will be placed at the head of the list.
LocalCandidates
contains peers whose transport process resides
on the local Erlang node while
RemoteCandidates
contains peers that have been communicated
from other nodes by services of the same name.
A callback that returns a peer() will be followed by a prepare_request/3 callback and, if the latter indicates that the request should be sent, by either handle_answer/4 or handle_error/4 depending on whether or not an answer message is received from the peer. If the transport becomes unavailable after prepare_request/3 then a new pick_peer/4 callback may take place to failover to an alternate peer, after which prepare_retransmit/3 takes the place of prepare_request/3 in resending the request. There is no guarantee that a pick_peer/4 callback to select an alternate peer will be followed by any additional callbacks since a retransmission to an alternate peer is abandoned if an answer is received from a previously selected peer.
The return values false
and {false, State}
(that is,
NewState = State
) are equivalent, as are {ok, Peer}
and
{Peer, State}
.
Note!
The diameter:service_opt() use_shared_peers
determines whether or
not a service uses peers shared from other nodes.
If not then RemoteCandidates
is the empty list.
Warning!
The return value {Peer, NewState}
is only allowed if
the Diameter application in question was configured with the
diameter:application_opt() {call_mutates_state, true}
.
Otherwise, the State
argument is always
the intial value as configured on the application, not any subsequent
value returned by a peer_up/3
or peer_up/3 callback.
Mod:prepare_request(Packet, SvcName, Peer) -> Action
Packet = packet()
SvcName = diameter:service_name()
Peer = peer()
Action = Send | Discard | {eval_packet, Action, PostF}
Send = {send, packet() | message()}
Discard = {discard, Reason} | discard
PostF = diameter:evaluable()}
Invoked to return a request for encoding and transport.
Allows the sender to use the selected peer's capabilities
to modify the outgoing request.
Many implementations may simply want to return {send, Packet}
A returned packet() should set the
request to be encoded in its
msg
field and can set the transport_data
field in order
to pass information to the transport process.
Extra arguments passed to diameter:call/4 can be used to
communicate transport (or any other) data to the callback.
A returned packet() can set
the header
field to a
#diameter_header{}
to specify values that should
be preserved in the outgoing request, values otherwise being those in
the header record contained in Packet
.
A returned length
, cmd_code
or application_id
is
ignored.
A returned PostF
will be evaluated on any encoded
#diameter_packet{}
prior to transmission, the bin
field
containing the encoded binary.
The return value is ignored.
Returning {discard, Reason}
causes the request to be aborted
and the diameter:call/4 for which the
callback has taken place to return {error, Reason}
.
Returning discard
is equivalent to returning {discard,
discarded}
.
Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action
Packet = packet()
SvcName = diameter:service_name()
Peer = peer()
Action = Send | Discard | {eval_packet, Action, PostF}
Send = {send, packet() | message()}
Discard = {discard, Reason} | discard
PostF = diameter:evaluable()}
Invoked to return a request for encoding and retransmission. Has the same role as prepare_request/3 in the case that a peer connection is lost an an alternate peer selected but the argument packet() is as returned by the initial prepare_request/3.
Returning {discard, Reason}
causes the request to be aborted
and a handle_error/4 callback to
take place with Reason
as initial argument.
Returning discard
is equivalent to returning {discard,
discarded}
.
Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result
Packet = packet()
Request = message()
SvcName = diameter:service_name()
Peer = peer()
Result = term()
Invoked when an answer message is received from a peer.
The return value is returned from diameter:call/4 unless the
detach
option was specified.
The decoded answer record and undecoded binary are in the msg
and bin
fields of the argument
packet() respectively.
Request
is the outgoing request message as was returned from
prepare_request/3 or prepare_retransmit/3.
For any given call to diameter:call/4 there is at most one handle_answer/4 callback: any duplicate answer (due to retransmission or otherwise) is discarded. Similarly, only one of handle_answer/4 or handle_error/4 is called.
By default, an incoming answer message that cannot be successfully
decoded causes the request process to fail, causing
diameter:call/4
to return {error, failure}
unless the detach
option was
specified.
In particular, there is no handle_error/4 callback in this
case.
The diameter:application_opt()
answer_errors
can be set to change this behaviour.
Mod:handle_error(Reason, Request, SvcName, Peer) -> Result
Reason = timeout | failover | term()
Request = message()
SvcName = diameter:service_name()
Peer = peer()
Result = term()
Invoked when an error occurs before an answer message is received
in response to an outgoing request.
The return value is returned from diameter:call/4 unless the
detach
option was specified.
Reason timeout
indicates that an answer message has not been
received within the time specified with the corresponding diameter:call_opt().
Reason failover
indicates
that the transport connection to the peer to which the request has
been sent has become unavailable and that not alternate peer was
not selected.
Mod:handle_request(Packet, SvcName, Peer) -> Action
Packet = packet()
SvcName = term()
Peer = peer()
Action = Reply | {relay, [Opt]} | discard | {eval|eval_packet, Action, PostF}
Reply = {reply, packet() | message()} | {answer_message, 3000..3999|5000..5999} | {protocol_error, 3000..3999}
Opt = diameter:call_opt()
PostF = diameter:evaluable()
Invoked when a request message is received from a peer. The application in which the callback takes place (that is, the callback module as configured with diameter:start_service/2) is determined by the Application Identifier in the header of the incoming request message, the selected module being the one whose corresponding dictionary declares itself as defining either the application in question or the Relay application.
The argument packet() has the following signature.
#diameter_packet{header = #diameter_header{}, avps = [#diameter_avp{}], msg = record() | undefined, errors = [Unsigned32() | {Unsigned32(), #diameter_avp{}}], bin = binary(), transport_data = term()}
The msg
field will be undefined
in case the request has
been received in the relay application.
Otherwise it contains the record representing the request as outlined
in diameter_dict(4).
The errors
field specifies any results codes identifying errors
found while decoding the request.
This is used to set Result-Code and/or Failed-AVP in a returned
answer unless the callback returns a #diameter_packet{}
whose errors
field is set to either a non-empty list of its
own, in which case this list is used instead, or the atom false
to disable any setting of Result-Code and Failed-AVP.
Note that the errors detected by diameter are of the 3xxx
and 5xxx series, Protocol Errors and Permanent Failures respectively.
The errors
list is empty if the request has been received in
the relay application.
The transport_data
field contains an arbitrary term passed into
diameter from the transport module in question, or the atom
undefined
if the transport specified no data.
The term is preserved if a message() is returned but must be set
explicitly in a returned packet().
The semantics of each of the possible return values are as follows.
{reply, packet() | message()}
Send the specified answer message to the peer.
In the case of a packet(), the
message to be sent must be set in the
msg
field and the header
field can be set to a
#diameter_header{}
to specify values that should be
preserved in the outgoing answer, appropriate values otherwise
being set by diameter.
{answer_message, 3000..3999|5000..5999}
Send an answer message to the peer containing the specified Result-Code. Equivalent to
{reply, ['answer-message' | Avps]
where Avps
sets the Origin-Host, Origin-Realm, the specified
Result-Code and (if the request contained one) Session-Id AVP's, and
possibly Failed-AVP as described below.
Returning a value other than 3xxx or 5xxx will cause the request
process in question to fail, as will returning a 5xxx value if the
peer connection in question has been configured with the RFC 3588
common dictionary diameter_gen_base_rfc3588
.
(Since RFC 3588 only allows 3xxx values in an answer-message.)
When returning 5xxx, Failed-AVP will be populated with the AVP of the
first matching Result-Code/AVP pair in the errors
field of the
argument packet(), if found.
If this is not appropriate then an answer-message should be
constructed explicitly and returned in a reply
tuple
instead.
{relay, Opts}
Relay a request to another peer in the role of a Diameter relay agent. If a routing loop is detected then the request is answered with 3005 (DIAMETER_LOOP_DETECTED). Otherwise a Route-Record AVP (containing the sending peer's Origin-Host) is added to the request and pick_peer/4 and subsequent callbacks take place just as if diameter:call/4 had been called explicitly. The End-to-End Identifier of the incoming request is preserved in the header of the relayed request.
The returned Opts
should not specify detach
.
A subsequent handle_answer/4
callback for the relayed request must return its first
argument, the packet() containing the answer message.
Note that the extra
option can be specified to supply arguments
that can distinguish the relay case from others if so desired.
Any other return value (for example, from a
handle_error/4 callback)
causes the request to be answered with 3002 (DIAMETER_UNABLE_TO_DELIVER).
discard
Discard the request. No answer message is sent to the peer.
{eval, Action, PostF}
Handle the request as if Action
has been returned and then
evaluate PostF
in the request process.
The return value is ignored.
{eval_packet, Action, PostF}
Like eval
but evaluate PostF
on any encoded
#diameter_packet{}
prior to transmission, the bin
field
containing the encoded binary.
The return value is ignored.
{protocol_error, 3000..3999}
Equivalent to {answer_message, 3000..3999}
.
Note!
Requests containing errors may be answered by diameter, without a
callback taking place, depending on the value of the
diameter:application_opt() request_errors
.