This chapter describes using the Diameter Base protocol implementation to create your own Diameter applications in Oracle Communications Converged Application Server.
Diameter is a peer-to-peer protocol that involves delivering attribute-value pairs (AVPs). A Diameter message includes a header and one or more AVPs. The collection of AVPs in each message is determined by the type of Diameter application, and the Diameter protocol also allows for extension by adding new commands and AVPs. Diameter enables multiple peers to negotiate their capabilities with one another, and defines rules for session handling and accounting functions.
Converged Application Server includes an implementation of the base Diameter protocol that supports the core functionality and accounting features described in RFC 3588 (http://www.ietf.org/rfc/rfc3588.txt
). Converged Application Server uses the base Diameter functionality to implement multiple Diameter applications, including the Sh, Rf, and Ro applications described later in this document.
You can also use the base Diameter protocol to implement additional client and server-side Diameter applications. The base Diameter API provides a simple, Servlet-like programming model that enables you to combine Diameter functionality with SIP or HTTP functionality in a converged application.
Note:
The Diameter protocol offers limited support for clustering for both client and server applications.The sections that follow provide an overview of the base Diameter protocol packages, classes, and programming model used for developing client and server-side Diameter applications. See also the following sections for information about using the provided Diameter protocol applications in your SIP Servlets:
Chapter 2, "Using the Diameter Sh Interface Application," describes how to access and manage subscriber profile data using the Diameter Sh application.
Chapter 3, "Using the Diameter Rf Interface Application for Offline Charging," describes how to issue offline charging requests using the Diameter Rf application.
Chapter 4, "Using the Diameter Ro Interface API for Online Charging," describes how to perform online charging using the Diameter Ro application.
This section describes the creation and deployment of Diameter applications using Java EE Common Dependency Injections (CDIs) in conjunction with Plain Old Java Objects (POJOs).
Example 1-1 shows an implementation of a Diameter Bean, ExamplePOJO, that listens to RoApplication messages.
Example 1-1 Diameter Bean Listening to RoApplication Messages
@DiameterBean(applicationId = "4") public class ExamplePOJO { @Inject DiameterSessionSource source; public void handleCCR(@Observes CCR ccr) { RoSession session = (RoSession) ccr.getSession(); SipApplicationSession sas = source.getApplicationSession(session); //... Business logic... } public void handleSTR(@Observes @DiameterObserver(code=275) Message msg) { RoSession session = (RoSession) msg.getSession(); SipApplicationSession sas = source.getApplicationSession(session); //... Business logic... } }
The annotation, @DiameterBean, marks the Java class as a Diameter Bean. The Diameter stack looks for Common Dependency Injection (CDI) observer methods for feeding the requests or responses to the Diameter Bean. The method arguments and the command code, if any, act as a filter for feeding the messages.
Such an architecture avoids the need for registering a listener for subsequent messages in the Diameter session, since the Diameter session information can be obtained from the message.
The RoApplication can then be injected into a SIP servlet as shown in Example 1-2.
Example 1-2 SIP Servlet RoApplication Injection
public class FooServlet { @Inject @DiameterContext(applicationId = "4") Application roApp; @Inject DiameterSessionSource source; void doInvite(SipServletRequest request) throws IOException { RoSession session = (RoSession) source.createSession(roApp, request.getApplicationSession()); CCR ccr = session.createCCR(RequestType.INITIAL); ccr.send(); } }
Note:
For information on the API used in this section, see the Oracle Communications Converged Application Server Java API Reference.The Diameter stack adds built-in CDI beans which can be injected into SIP Servlets and Diameter Beans.
This bean acts as a source for Diameter session objects, and helps SIP Servlet applications to associate a diameter Session with a SipApplicationSession and also obtain SipApplicationSession information from a Diameter session.
A com.bea.wcp.diameter.Application can be injected with a CDI bean like a SIP servlet or a Diameter bean. The exact subtype of the Application is based on the annotation @DiameterContext.
Example 1-3 shows injecting an instance of RoApplication into a SIP Servlet.
Application subtypes, such as RoApplication, RfApplication, ReApplication, RxApplication, GxApplication, GxxApplication, ShApplication, SyApplication can be injected into a CDI bean directly, and do not require the @DiameterContext annotation.
Example 1-4 shows injecting an instance of RoApplication, RfApplication, and ShApplication into a SIP servlet.
The Diameter stack selects the bean to handle incoming requests based on the following different criteria:
Application Identifier
Peer (host/realm)
Origin (origin host/ origin realm)
Destination (destination host / destination realm)
The stack finds the most specific Diameter bean to handle the message, irrespective of the application in which it is packaged. The following rules determine the most specific bean:
If the bean contains and matches all the selection criteria annotations, then it is considered most specific.
If the bean contains and matches a fewer number of selection critera annotations, then priority is given in the following order:
Application then Peer then Destination then Origin.
For matching a node (Peer, Origin or Destination), priority is given to host rather than realm.
Example 1-5 shows a bean with a more complicated set of selection criteria. When a Diameter message comes in, ExampleBean1 is selected first, followed by the remaining beans in the following order:
ExampleBean2 then ExampleBean3 then ExampleBean4 then ExampleBean5
Example 1-5 Diameter Bean Selection Criteria
@DiameterBean( applicationId = 4, peers = { @DiameterNode(host = "ro.server", realm = "weblogic.com") }, origins = { @DiameterNode(host = "originserver", realm = "originserver.com"), @DiameterNode(host = "ro.client", realm = "weblogic.com") }, destinations = { @DiameterNode(host = "ro.server", realm = "weblogic.com") }) public class ExampleBean1 { } //specify peers. @DiameterBean( applicationId = 4, peers = { @DiameterNode(host = "ro.server", realm = "weblogic.com") }, public class ExampleBean2 { } //specify origins. @DiameterBean( applicationId = 4, origins = { @DiameterNode(host = "originserver", realm = "originserver.com"), @DiameterNode(host = "ro.client", realm = "weblogic.com") }, public class ExampleBean3 { } //destinations specify both host and realm. @DiameterBean( applicationId = 4, destinations = { @DiameterNode(host = "ro.server", realm = "weblogic.com") }) public class ExampleBean3 { } //destinations only specify host. @DiameterBean( applicationId = 4, destinations = { @DiameterNode(host = "ro.server") }) public class ExampleBean4 { } //destinations only specify realm. @DiameterBean( applicationId = 4, destinations = { @DiameterNode(realm = "weblogic.com") }) public class ExampleBean5 { } }
A Diameter bean may need to filter the messages based on the command code of the message. For example, if the Diameter bean wants to receive Session Terminated Answer (STA) and Credit Control Answer (CCA) messages on two different methods, it can do so by using the @DiameterObserver annotation. If the @DiameterObserver annotation is specified in the method, then the code injected by @DiameterObserver will be used by the Diameter stack (CDI) for delivering the message to the correct method.
If two methods have the same @DiameterObserver annotation, the Diameter bean selects the method where the parameter is the same type as the message.
Example 1-6 illustrates this behavior.
Example 1-6 Filtering Observer Methods
public void handleCCA(@Observes @DiameterObserver(code=272) CCA cca) { } public void handleAnswer(@Observes @DiameterObserver(code=272) Answer answer) { } public void handleSTA(@Observes @DiameterObserver(code=275) Answer answer) { } public void handleCCR(@Observes @DiameterObserver(code=272) CCR ccr) { } public void handleRequest(@Observes @DiameterObserver(code=272) Request request) { } public void handleMessage(@Observes Message message) { }
In Example 1-6 the following filtering occurs:
When a CCA message comes in, it is delivered to handleCCA() not handleAnswer(), because the handleCCA() parameter CCA matches the CCA message type exactly.
When an Answer message (not a CCA) with the command code 272 comes in, it it is delivered to the handleAnswer() method.
When an STA message comes in, it is delivered to the handleSTA() method.
When a CCR message comes in, it is delivered to the handleCCR() method rather than the handleRequest() method.
When a request message (not a CCR) with command code 272 comes in, it is delivered to the handleRequest() method.
When other Diameter messages come in, they are delivered to the handleMessage() method.
Since the host and realm values of a @DiameterNode may need to be updated frequently, Converged Application Server leverages Java Enterprise Edition Environmental Entries, and lets you define them as variables, placing their values in SIP application deployment descriptors, such as sip.xml, or web.xml rather than hard coding them in source code. The deployment descriptors can be then be modified without requiring an application recompile. Alternatively, you can use WebLogic deployment plans to modify environmental variables.
In a @DiameterNode if host or realm is defined using the pattern, ${xxxx}, the xxxx is treated as a variable name. That variable name is matched to env-entry-name elements in sip.xml or web.xml, and, when a match occurs, the associated value is returned, replacing ${xxxx}.
The host or realm variable can defined by using letters, digits, hyphen, dots and underscore character.
Example 1-7 is a typical sip.xml configuration file with two env-entry elements containing env-entry-name child elements.
Example 1-7 sip.xml Configuration File
<?xml version="1.0" encoding="UTF-8"?> <sip-app xmlns="http://www.jcp.org/xml/ns/sipservlet" xmlns:javaee="http://java.sun.com/xml/ns/javaee"> <app-name>FooServlet</app-name> <javaee:display-name>diameter abstract API Test</javaee:display-name> <distributable /> <javaee:env-entry> <javaee:description>peer server host name</javaee:description> <javaee:env-entry-name>peer-server</javaee:env-entry-name> <javaee:env-entry-type> java.lang.String </javaee:env-entry-type> <javaee:env-entry-value>ro.client</javaee:env-entry-value> </javaee:env-entry> <javaee:env-entry> <javaee:description>peer server realm</javaee:description> <javaee:env-entry-name>peer-realm</javaee:env-entry-name> <javaee:env-entry-type> java.lang.String </javaee:env-entry-type> <javaee:env-entry-value>example.com</javaee:env-entry-value> </javaee:env-entry> </sip-app>
In Example 1-8, the host value for @DiameterNode is defined as ${peer-server}. To retrieve the value of peer-server from sip.xml in Example 1-7, the env-entry elements are searched for an env-entry-name element that matches peer-server. The match in this case is the env-entry-value, ro.client. Likewise, the realm value, peer-realm from ${peer-realm} matches the env-entry-name, peer-realm, which then returns the env-entry-value, example.com.
Example 1-8 Referencing sip.xml Configuration Parameters
@DiameterBean (applicationId= 4, peers= {@DiameterNode (host = "${peer-server}", realm ="${peer-realm}")}) public class ExampleBean { }
The realm and host values of @DiameterNode can also be changed using a WebLogic deployment plan.
Example 1-9 illustrates a WebLogic Deployment Plan file, plan.xml.
Example 1-9 WebLogic Deployment Plan
<?xml version='1.0' encoding='UTF-8'?> <deployment-plan xmlns="http://xmlns.oracle.com/weblogic/deployment-plan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/deployment-plan http://xmlns.oracle.com/weblogic/deployment-plan/1.0/deployment-plan.xsd"> <application-name>thirdpartyprotocol</application-name> <variable-definition> <variable> <name>new_peer-server</name> <value>ro.client2</value> </variable> <variable> <name>new_peer-realm</name> <value>example.com2</value> </variable> </variable-definition> <module-override> <module-name>thirdpartyprotocol.war</module-name> <module-type>war</module-type> <module-descriptor external="false"> <root-element>web-app</root-element> <uri>WEB-INF/web.xml</uri> </module-descriptor> <module-descriptor external="false"> <root-element>sip-app</root-element> <uri>WEB-INF/sip.xml</uri> <variable-assignment> <name>new_peer-server</name> <xpath>/sip-app/env-entry/[env-entry-name="peer-server"]/env-entry-value</xpath> <operation>replace</operation> </variable-assignment> <variable-assignment> <name>new_peer-realm</name> <xpath>/sip-app/env-entry/[env-entry-name="peer-realm"]/env-entry-value</xpath> <operation>replace</operation> </variable-assignment> </module-descriptor> </module-override> </deployment-plan>
In Example 1-9 two variables are defined, new_peer-server and new_peer-realm, which are then used to replace the env-entry-values in sip.xml after plan.xml is deployed. You can use the WebLogic Administration Console to specify a deployment plan for your application, or you can use the weblogic.Deployer to deploy an application with the -plan parameter:
java weblogic.Deployer -adminurl http://hostname:port -user username -password password -deploy -name application-name -source application-name.war -targets server-name -stage -plan plan.xml
This section describes developing Diameter applications using version 1.x legacy Java techniques.
This section describes requirements for compiling legacy Diameter applications as well as configuring Diameter nodes.
The wlssdiameter.jar file is part of the exposed Diameter API. To compile against this API, you must access the wlssdiameter.jar, which is located in the directory:
Middleware_Home/Oracle_Home/wlserver/sip/server/lib/
Where MW_home
is the directory in which the Converged Application Server software is installed (the installation program used to install Converged Application Server refers to this as Middleware Home). For example:
/Oracle/Middleware/Oracle_Home/wlserver/sip/server/lib/
A Diameter node is represented by the com.bea.wcp.diameter.Node
class. A Diameter node may host one or more Diameter applications, as specified in the diameter.xml
configuration file, located in the directory: Middleware_Home/Oracle_Home/user_projects/domains/domain_name/config/custom/
Where Middleware_Home is the directory in which the Converged Application Server software is installed, and domain_name is the name of the Diameter domain. For example:
/Oracle/Middleware/Oracle_Home/user_projects/domains/Diameter_domain/config/custom
Diameter nodes are generally configured and started as part of a Converged Application Server instance. However, for development and testing purposes, you can also run a Diameter node as a standalone process. To do so:
Set the environment for the Diameter domain using the setDomainEnv.sh
(UNIX) or setDomainEnv.cmd
(Windows) command located in the directory: Middleware_Home/Oracle_Home/user_projects/domains/domain_name/diameter/bin/
Where Middleware_Home is the directory where you installed the Converged Application Server software and my_domain is the name of the domain's directory. For example:
cd /Oracle/Middleware/Oracle_Home/user_projects/domains/Diameter_domain/diameter/bin ./setDomainEnv.sh
Make the directory containing the diameter.xml
configuration file for the Diameter Node you want to start your working directory. For example:
cd /Oracle/Middleware/Oracle_Home/user_projects/domains/Diameter_domain/config/custom
Set the Java class path for the Diameter domain to the file: com.bea.core.process_5.4.0.0.jar
java -classpath $CLASSPATH:/Oracle/Middleware/wlserver/server/lib/consoleapp/APP-INF/lib/com.bea.core.process_5.4.0.0.jar
Start the Diameter Node, specifying the diameter.xml
configuration file to use with the domain:
java com.bea.wcp.diameter.Node ./diameter.xml
All classes in the Diameter base protocol API reside in the com.bea.wcp.diameter
package. Table 1-1 describes the key classes, interfaces, and exceptions in this package.
Table 1-1 Key Elements of the Diameter Base Protocol API
Category | Element | Description |
---|---|---|
Diameter Node |
Node |
A class that represents a Diameter node implementation. A diameter node can represent a client- or server-based Diameter application, as well as a Diameter relay agent. |
Diameter Applications |
Application, ClientApplication |
A class that represents a basic Diameter application. ClientApplication extends Application for client-specific features such as specifying destination hosts and realms. All Diameter applications must extend one of these classes to return an application identifier. The classes can also be used directly to create new Diameter sessions. |
Diameter Applications |
ApplicationId |
A class that represents the Diameter application ID. This ID is used by the Diameter protocol for routing messages to the appropriate application. The |
Diameter Applications |
Session |
A class that represents a Diameter session. Applications that perform session-based handling must extend this class to provide application-specific behavior for managing requests and answering messages. |
Message Processing |
Message, Request, Answer |
The Message class is a base class used to represent request and answer message types. Request and Answer extend the base class. |
Message Processing |
Command |
A class that represents a Diameter command code. |
Message Processing |
RAR, RAA |
These classes extend the |
Message Processing |
ResultCode |
A class that represents a Diameter result code, and provides constant values for the base Diameter protocol result codes. |
AVP Handling |
Attribute |
A class that provides Diameter attribute information. |
AVP Handling |
Avp, AvpList |
Classes that represent one or more attribute-value pairs in a message. AvpList is also used to represent AVPs contained in a grouped AVP. |
AVP Handling |
Type |
A class that defines the supported AVP datatypes. |
Error Handling |
DiameterException |
The base exception class for Diameter exceptions. |
Error Handling |
MessageException |
An exception that is raised when an invalid Diameter message is discovered. |
Error Handling |
AvpException |
An exception that is raised when an invalid AVP is discovered. |
Supporting Interfaces |
Enumerated |
An enum value that implements this interface can be used as the value of an AVP of type INTEGER32, INTEGER64, or ENUMERATED. |
Supporting Interfaces |
SessionListener |
An interface that applications can implement to subscribe to messages delivered to a Diameter session. |
Supporting Interfaces |
MessageFactory |
An interface that allows applications to override the default message decoder for received messages, and create new types of The default decoding process begins by decoding the message header from the message bytes using an instance of |
In addition to these base Diameter classes, accounting-related classes are stored in the com.bea.wcp.diameter.accounting
package, and credit-control-related classes are stored in com.bea.wcp.diameter.cc
. See Chapter 4, "Using the Diameter Ro Interface API for Online Charging" and Chapter 3, "Using the Diameter Rf Interface Application for Offline Charging" for more information about classes in these packages.
In order to access a Diameter application, a deployed application (such as a SIP Servlet) must obtain the Diameter Node instance and request the application. Example 1-10 shows the sample code used to access the Rf application.
All Diameter applications must extend either the base Application
class or, for client applications, the ClientApplication
class. The model for creating a Diameter application is similar to that for implementing Servlets in the following ways:
Diameter applications override the init()
method for initialization tasks.
Initialization parameters configured for the application in diameter.xml
are made available to the application.
A session factory is used to generate new application sessions.
Diameter applications must also implement the getId()
method to return the proper application ID. This ID is used to deliver Diameter messages to the correct application.
Applications can optionally implement rcvRequest()
or rcvAnswer()
as needed. By default, rcvRequest()
answers with UNABLE_TO_COMPLY, and rcvRequest()
drops the Diameter message.
Example 1-11 shows a simple Diameter client application that does not use sessions.
Example 1-11 Simple Diameter Application
public class TestApplication extends ClientApplication { protected void init() { log("Test application initialized."); } public ApplicationId getId() { return ApplicationId.BASE_ACCOUNTING; } public void rcvRequest(Request req) throws IOException { log("Got request: " + req.getHopByHopId()); req.createAnswer(ResultCode.SUCCESS).send(); } }
Applications that perform session-based handling must extend the base Session class to provide application-specific behavior for managing requests and answering messages. If you extend the base Session class, you must implement either rcvRequest()
or rcvAnswer()
, and may implement both methods.
The base Application class is used to generate new Session objects. After a session is created, all session-related messages are delivered directly to the session object. The Converged Application Server container automatically generates the session ID and encodes the ID in each message. Session attributes are supported much in the same fashion as attributes in SipApplicationSession
.
Example 1-12 shows a simple Diameter session implementation.
Example 1-12 Simple Diameter Session
public class TestSession extends Session { public TestSession(TestApplication app) { super(app); } public void rcvRequest(Request req) throws IOException { getApplication().log("rcvReuest: " + req.getHopByHopId()); req.createAnswer(ResultCode.SUCCESS).send(); } }
To use the sample session class, the TestApplication
in Example 1-11 would need to add a factory method:
public class TestApplication extends Application { ... public TestSession createSession() { return new TestSession(this); } }
TestSession
could then be used to create new requests as follows:
TestSession session = testApp.createSession(); Request req = session.creatRequest(); req.sent();
The answer is delivered directly to the Session object.
Note:
While a typical Converged Application Server Diameter client application does not need to concern itself with session management, a Diameter server applications must calldiameterSession.terminate()
to invalidate a session when it is no longer needed.The base Message
class is used for both Request and Answer message types. A Message always includes an application ID, and optionally includes a session ID. By default, messages are handled in the following manner:
The message bytes are parsed.
The application and session ID values are determined.
The message is delivered to a matching session or application using the following rules:
If the Session-Id AVP is present, the associated Session is located and the session's rcvMessage()
method is called.
If there is no Session-Id AVP present, or if the session cannot be located, the Diameter application's rcvMessage()
method is called.
If the application cannot be located, an UNABLE_TO_DELIVER response is generated.
The message type is determined from the Diameter command code. Certain special message types, such as RAR, RAA, ACR, ACA, CCR, and CCA, have getter and setter methods in the Message
object for convenience.
Either a Session
or Application
can originate and receive request messages. Requests are generated using the createRequest()
method. You must supply a command code for the new request message. For routing purposes, the destination host or destination realm AVPs are also generally set by the originating session or application.
Received answers can be obtained using Request.getAnswer()
. After receiving an answer, you can use getSession()
to obtain the relevant session ID and getResultCode()
to determine the result. You can also use Answer.getRequest()
to obtain the original request message.
Requests can be sent asynchronously using the send()
method, or synchronously using the blocking sendAndWait()
method. Answers for requests that were sent asynchronously are delivered to the originating session or application. You can specify a request timeout value when sending the message, or can use the global request-timeout
configuration element in diameter.xml
. An UNABLE_TO_DELIVER result code is generated if the timeout value is reached before an answer is delivered. getResultCode()
on the resulting Answer returns the result code.
New answer messages are generated from the Request
object, using createAnswer()
. All generated answers should specify a ResultCode and an optional Error-Message AVP value. The ResultCode
class contains pre-defined result codes that can be used.
Answers are delivered using the send()
method, which is always asynchronous (non-blocking).
A Diameter command code determines the message type. For instance, when sending a request message, you must supply a command code.
The Command
class represents pre-defined commands codes for the Diameter base protocol, and can be used to create new command codes. Command codes share a common name space based on the code itself.
The define()
method enables you to define codes, as in:
static final Command TCA = Command.define(1234, "Test-Request", true, true);
The define()
method registers a new Command, or returns a previous command definition if one was already defined. Commands can be compared using the reference equality operator (==).
Attribute Value Pair (AVP) is a method of encapsulating information relevant to the Diameter message. AVPs are used by the Diameter base protocol, the Diameter application, or a higher-level application that employs Diameter.
The Avp
class represents a Diameter attribute-value pair. You can create new AVPs with an attribute value in the following way:
Avp avp = new Avp(Attribute.ERROR_MESSAGE, "Bad request");
You can also specify the attribute name directly, as in:
Avp avp = new Avp("Error-Message", "Bad request");
The value that you specify must be valid for the specified attribute type.
To create a grouped AVP, use the AvpList
class, as in:
AvpList avps = new AvpList(); avps.add(new Avp("Event-Timestamp", 1234)); avps.add(new Avp("Vendor-Id", 1111));
You can create new attributes to extend your Diameter application. The Attribute
class represents an AVP attribute, and includes the AVP code, name, flags, optional vendor ID, and type of attribute. The class also maintains a registry of defined attributes. All attributes share a common namespace based on the attribute code and vendor ID.
The define()
method enables you to define new attributes, as in:
static final Attribute TEST = Attribute.define(1234, "Test-Attribute", 0, Attribute.FLAG_MANDATORY, Type.INTEGER32);
Table 1-2 lists the available attribute types and describes how they are mapped to Java types.
The define()
method registers a new attribute, or returns a previous definition if one was already defined. Attributes can be compared using the reference equality operator (==).
The Diameter API enables you to create converged applications that utilize both SIP and Diameter functionality. A SIP Servlet can access an available Diameter application through the Diameter Node, as shown in Example 1-13.
Example 1-13 Accessing the Rf Application from a SIP Servlet
ServletContext sc = getServletConfig().getServletContext(); Node node = (Node) sc.getAttribute("com.bea.wcp.diameter.Node"); RfApplication rfApp = (RfApplication) node.getApplication(Charging.RF_APPLICATION_ID);
SIP uses Call-id (the SIP-Call-ID header) to identify a particular call session between two users. Converged Application Server automatically links a Diameter session to the currently-active call state by encoding the SIP Call-id into the Diameter session ID. When a Diameter message is received, the container automatically retrieves the associated call state and locates the Diameter session. A Diameter session is serializable, so you can store the session as an attribute in a the SipApplicationSession
object, or vice versa.
Converged applications can use the Diameter SessionListener
interface to receive notification when a Diameter message is received by the session. The SessionListener
interface defines a single method, rcvMessage()
. Example 1-14 shows an example of how to implement the method.
Example 1-14 Implementing SessionListener
Session session = app.createSession(); session.setListener(new SessionListener() { public void rcvMessage(Message msg) { if (msg.isRequest()) System.out.println("Got request!"); } });
Note:
The SessionListener
implementation must be serializable for distributed applications.