Jump to content
HP.com Home Products and Services Support and Drivers Solutions How to Buy
» Contact HP
HP.com home

The Diameter Base Protocol Java API: A Quick Tutorial

Overview

Learn how to develop a Java application based on the Diameter Base Protocol API.

On this page  Skip past table of contents

Introduction

This page explains the different steps you have to go through when you want to develop a Java application based on the Diameter Base Protocol API. HP OpenCall Diameter API has been designed in order to be as close as possible to the JAIN typical architecture despite JAIN Diameter is not specified. Thus, it is very easy for a developer already familiar with JAIN SIP or JAIN TCAP to develop using this Diameter API (For more information on the JAIN specification, visit the JAIN homepage: http://java.sun.com/products/jain/ ). Note HP OpenCall Diameter also provides a set of C++ APIs.

This tutorial targets the Diameter Base protocol as defined in RFC3588. For the development of applications using the Sh, Ro, Rf, CCA, Cx/Dx interfaces provided by HP OpenCall Diameter, it is important to start with this tutorial, and then only to read the tutorial matching the target application.

For more details and information on the HP OpenCall Diameter API usage, please refer to the documentation and Javadoc available with the SDK.

This tutorial aims at giving you the ability to develop a basic application using the HP OpenCall Diameter Java API. In order to take maximum benefits from it, you should meet the following prerequisites:
  • Basic knowledge of object-oriented and Java development.
  • Basic knowledge of the Diameter protocol.

Presentation of the HP OpenCall Diameter Java API

The Diameter Java API is provided under the form of a jar file. Refer to the “Getting Started” page section “Architecture Overview” for more details on the HP OpenCall Diameter stack structure.

An application using HP OpenCall Diameter has to perform three main tasks:
  • Initialize and manage Diameter routes
  • Receive and handle events and Diameter messages
  • Send Diameter messages

The initialization of the Diameter routes, management of the realm-based routing table and opening of ports on which remote nodes can connect is managed by a DiameterStack (identified by its local realm, which is an administrative domain).

The Diameter application (identified by its Application-Id and Vendor-Id, within the scope of the realm defined in the DiameterStack) is responsible of

Diameter Application

The DiamBaseClient & DiamBaseServer example applications

DiamBaseClient.java and DiamBaseServer.java are client and server programs that implement an imaginary Diameter application, myDiameterApp, using on a single command (i.e message) called getPassword. This application makes use of two specific AVPs (attribute-value pairs, i.e. Diameter parameters) called Username and Password.

The complete source code for the applications is available here: DiamBaseClient.java and DiamBaseServer.java. These examples may be used as a starting point to develop a real-world application on top of the Diameter Base Protocol API.

In this tutorial, we will review in details the steps that are performed by a typical Diameter application. For the sake of simplicity, however, the source code shown in this page is not an exact copy of what you will find in DiamBaseClient.java and DiamBaseServer.java. However, the majority of the operations that are performed by these example applications are explained below.

Note also that the code below is included here for conceptual purposes only and may lack code grammar coherence (mostly: check for null references, and catch DiameterExceptions ).

The following diagram describes the behavior of the example applications:

DiameterBaseClient - DiameterBaseServer

Application initialization


Several steps are required to initialize the Diameter stack before messages can be exchanged, namely:
  • Create a DiameterStack instance
  • Register the Diameter application to the Diameter stack
  • Create listening points to bind to local transport addresses
  • Configure routes and connect to Diameter peers

Creating a DiameterStack instance

An instance of the Diameter stack can be created as follows:
<import com.hp.opencall.diameter.*;

<DiameterFactory myFactory;
<DiameterStack myStack;
<Properties myStackProperties = new Properties();
			
<myFactory = DiameterFactory.getInstance();

<myStack =  myFactory.createDiameterStack(“ExampleRealm”, 
<“localhost.localdomain.com”, myStackProperties);

This code creates a Diameter stack for use by the local Diameter node, which Fully Qualified Domain Name (FQDN) is localFQDN (localhost, in the case of DiamBaseClient.java) and which Origin Realm is localRealm. The properties allow specifying for instance the transport to be used (SCTP, TCP).

The DiameterFactory is a singleton object that allows the creation of the DiameterStack object itself. Different DiameterStack instances serving different realms can be created from the same DiameterFactory.

This piece of code will be integrated in the DiamBaseClient application constructor.

Declaring a Diameter application and its dictionary

A Diameter application, as defined by RFC3588, has the following characteristics:

The Commands and AVPs defined by the RFC3588 are automatically loaded in the DiameterStack during its creation. It is possible to extend the existing dictionary with new AVPs, commands and applications by means of an XML string containing the additional elements. Here is the dictionary extension used for the DiamBase example applications; it defines a new application, a new getPassword command, and two AVPs, Username and Password.

The peer Diameter nodes exchanging custom AVPs or commands must have the AVP or command defined in it local dictionary. In our example, both the client and the server codes must extend the standard dictionary.

For more complex examples you may refer to the Diameter Base Protocol dictionary or the 3GPP Sh Interface dictionary in the Javadoc provided with the SDK.

String myDictionary = " <?xml version=\"1.0\" encoding=\"UTF-8\"?> 	\n"
		+ " <!DOCTYPE dictionary SYSTEM \"dictionary.dtd\"> 	\n"
		+ " <dictionary>					\n"
		+ " <vendor id=\"11\" name=\"hp\">			\n"
		+ " 	<avp name=\"Username\"			\n"
		+ " 		code=\"1\"			\n"
		+ " 		mandatory=\"must\"			\n"
		+ " 		may-encrypt=\"no\"			\n"
		+ " 		protected=\"mustnot\"		\n"
		+ " 		type=\"UTF8String\">		\n"
		+ " 	</avp>					\n"
		+ " 	<avp name=\"Password\"			\n"
		+ " 		code=\"2\"			\n"
		+ " 		mandatory=\"must\"			\n"
		+ " 		may-encrypt=\"no\"			\n"
		+ " 		protected=\"mustnot\"		\n"
		+ " 		type=\"UTF8String\">		\n"
		+ " 	</avp>					\n"
		+ " </vendor>					\n"
		+ " <application id=\"123\"				\n"
		+ " 		name=\"myDiameterApp\"		\n"
		+ " 		vendor=\"hp\"			\n"
		+ " 		service-type=\"Auth\">		\n"
		+ " <command name=\"getPassword\" code=\"10\"/>	\n"
		+ " </application>					\n"
		+ " </dictionary>					\n";

For the details about the different tags, refer to RFC3588 and the HP OpenCall Diameter Javadoc.

For increased flexibility in a real application you may want to read the XML syntax description from a file rather than having it embedded in the Java source code. This way, it becomes possible to change the mapping between names and codes without recompiling the application.

Once the application dictionary is defined, the actual application objects may be instantiated. A Diameter application is represented by instances of the class DiameterProvider and the interface DiameterListener.

DiameterListener represents the Diameter application interface. It provides a set of callback functions that must be implemented by the application in order to handle incoming events and messages. It is used by the Diameter base protocol stack to deliver messages and events to the user code.

The DiameterProvider is used by the user code to send messages and control the application behavior.

A typical Diameter application implements the DiameterListener, as follows:

class DiamBaseClientApplication implements	DiameterListener 
{
	
//DiamBaseClient constructor
DiameterBaseClientApplication (String[] args) throws DiameterException {

	// creates a DiameterFactory
	DiameterFactory myFactory;
	myFactory = DiameterFactory.getInstance();
	// create and set myStack properties
	// disable SCTP transport for Diameter
	Properties myStackProperties = new Properties();
myStackProperties.setProperty("com.hp.opencall.diameter.DISABLE_SCTP", "true"); // creates a DiameterStack String localRealm = \n“ExampleRealm\n”; String localFQDN = \n“localhost.localdomain.com\n”; myStack = myFactory.createDiameterStack(localRealm, localFQDN, myStackProperties); // extend the diameter base dictionary with the custom XML. myStack.extendGrammar(myDictionary); // create provider, and attach listener to the provider myProvider = myStack.createDiameterProvider("myDiameterApp", null); myProvider.setDiameterListener(this); // Local address binding and route creation: see next sections } // end of constructor

Binding to local transport addresses

If your application needs to listen for incoming connections on one or several transport addresses, it has to create one or several instances of the DiameterListeningPoint interface.

For details on the URI specification, please refer to the documentation of the createDiameterListeningPoint method. Note that there is usually no need for the user application to keep the references on the listening points, since they can be retrieved later by calling DiameterStack.getDiameterListeningPoints.

class DiamBaseClientApplication implements	DiameterListener {

  // The code presented in the previous section stands here.
  
  // create a listening point for incoming messages
  myStack.createDiameterListeningPoint(“aaa://localhost.localdomain.com:
  port=3868:transport=tcp”);
  
} // end of constructor

Configuring routes and connecting to Diameter peers

A Diameter client application can declare remote peers by using the createDiameterRoute method. The code fragment below configures a Diameter realm, peerRealm.
The metric value (1 in the example application) is the highest priority. If a second route is created for the same realm and application with the same value, a Session-Id based round-robin algorithm is applied to outgoing messages.
If the value of the second route is higher, the priority is considered lower and the route is be used only if the higher priority route is unavailable.
Please refer to the documentation of the createDiameterRoute method for details.

class DiamBaseClientApplication implements	DiameterListener {

	// The code presented in the previous sections stands here.
	
	// create a route to peer
	String peerRealm = “ExampleRealm”;
	String peerURI =
  ”aaa://localhost.localdomain.com:
  port=3869:transport=tcp”
	myStack.createDiameterRoute("myDiameterApp", peerRealm, peerURI, 1);
		
} // end of constructor
Notes on routes setup and management:

Application tasks


Once initialized, the application has to

Managing unkown peers connections

As soon as the listening point has been created, the Diameter stack is ready to accept incoming connections from remote peers. If the Diameter stack receives a connection request from a peer that as not been declared in the routing table, then the isUnknownPeerAuthorized() callback method of the DiameterListener interface is called. The connection is accepted only if this method returns true. The IsUnknownPeerAuthorized() callback method must be implemented by the Diameter application developer. It might simply return consistently true or false, or implement more complexe rules to define which type of unknown peer has the right to connect to the local Diameter node (e.g. realm-based).

public boolean isUnknownPeerAuthorized(DiameterMessage incomingCER) {
		return true;
	}

Handling events

The Diameter stack delivers information to the user application by creating instances of the DiameterEvent class and passing these objects to the DiameterListener.processEvent() method.

There are currently two kinds of events that further derive from the DiameterEvent class:


• The DiameterRealmStateChangeEvent class is used to notify the application of the reach ability or unreachability of a remote realm, as a result of peers coming up or down. This is important because the Diameter stack will not accept an outgoing message for which the remote realm is not available. Therefore the application should wait until the realm is available before sending requests.

• The DiameterSessionEvent class is used for all events that are related to a Diameter session, such as received messages, session timeouts, and session errors. Therefore, this class is further derived in DiameterMessageEvent, DiameterSessionDeletedEvent, DiameterSessionErrorEvent, DiameterTimeoutEvent.

Here is a typical implementation of the DiameterListener.processEvent() method, checking for the occurrence of all types of events.

// DiameterListener methods implementation
public void processEvent(DiameterEvent event) {
	
	// discover type of event
	if (event instanceof DiameterRealmStateChangeEvent) {
		DiameterRealmStateChangeEvent rsc = 
		(DiameterRealmStateChangeEvent) event;
		if (rsc.isRealmAvailable()) {
			System.out.println("Realm "+rsc.getRealm()+" 
			is available."); 
		} else {
				System.out.println("Realm "+rsc.getRealm()+" 
				is unavailable."); 			
		}
	} else {
		if (event instanceof DiameterSessionErrorEvent) {
		   System.out.println("Session error: "+ event.toString() );
		} else {
			if (event instanceof DiameterTimeoutEvent) {
			   // manage time outs
			   System.out.println("Timeout: "+ event.toString());
			} else {
				if (event instanceof 
				DiameterSessionDeletedEvent) {
				 // manage session deletion
				 System.out.println("Session deleted: 
				 "+ event.toString() );
			}
			// Message event management: see next section
		}
	}
}

Creating messages

HP OpenCall Diameter provides a DiameterMessageFactory that provides methods enabling the creation of RFC3588 compliant and extended dictionary compliant messages and AVPs.

Since the getPassword command and the Username AVP have been added to the standard dictionary by means of the extendXMLgrammar() method, the DiameterMessageFactory allows the creation of these items.

The HP OpenCall Diameter stack takes care of automatically inserting some AVPs and header values into the outgoing Diameter messages. The following table shows which values are concerned.

Header field or AVP May be added by the stack? Overwritten if present/non-zero? Value
End-To-End Identifier Yes Yes Pseudo-random initial value as defined in RFC3588 section 3, incremented for each request
Hop-By-Hop Identifier Yes No Pseudo-random initial value, incremented for each request
Session-Id AVP Yes (see details below) No As defined in RFC3588 section 8.8
Origin-Host AVP Yes Yes String provided at stack creation, returned by DiameterStack.getLocalFQDN()
Origin-Realm AVP Yes Yes String provided at stack creation, returned by DiameterStack.getLocalRealm()
Auth-Application-Id AVP Yes, if Application-Id field is 0 and application service type is defined as Auth in dictionary Yes Application identifier as declared in dictionary
Acct-Application-Id AVP Yes, if Application-Id field is 0 and application service type is defined as Acct in dictionary Yes Application identifier as declared in dictionary
Vendor-Specific-Application-Id AVP with Auth-Application-Id AVP Yes, if Application-Id field is 0 and application service type is defined as Auth in dictionary Yes Vendor and application identifiers as declared in dictionary
Vendor-Specific-Application-Id AVP with Acct-Application-Id AVP Yes, if Application-Id field is 0 and application service type is defined as Acct in dictionary Yes Vendor and application identifiers as declared in dictionary

Therefore, it is enough to add only the following to an outgoing message:

• Application-Id and Command-Code. Set by DiameterMessageFactory.createMessage().
• Command flags, if required
• Destination-Realm AVP
• Any application-defined AVPs: in our case, the Username AVP

The Session-Id AVP is automatically added by the stack when the message is sent (through a DiameterSession object: DiameterSession.sendMessage())

The following code (extracted from DiamBaseClient.java ) creates a getPassword request message:

void createSessionAndSendCustomMessage() {
		
 // get the Diameter message factory, to build the message		
 DiameterMessageFactory messageFactory = myStack.getDiameterMessageFactory();
		
 //build request message getPassword for app myDiameterApplication
 DiameterMessage request = messageFactory.createMessage
 (true,"getPassword","myDiameterApp");
 //add mandatory AVP
 {
 	DiameterAVP avp = messageFactory.createOctetStringAVP
 	("Destination-Realm","base",peerRealm);
 	request.add(avp);
 }
 //add vendor specific AVP
 {
 	DiameterAVP avp = messageFactory.createOctetStringAVP
 	("Username","hp","toto");
 	request.add(avp);
 }
 
 //Session creation and message sending is treated in the next section
}

Sending messages

Once the initialization and configuration phase is over, the Diameter application is able to send and receive Diameter messages.

The messages that have the same Session-Id AVP are attached to the same instance of the DiameterSession interface.

To send a request for a new Diameter session, the application calls the createClientDiameterSession method from the DiameterProvider interface. Then it can send the request by calling DiameterSession.sendMessage.

Likewise, an incoming request can be handled by calling createServerDiameterSession to create a new server session, then using DiameterSession.sendMessage to send the response.

Using the session-stateful mode enables

The following code (extracted from DiamBaseClient.java ) statefully sends a getPassword request message:

void createSessionAndSendCustomMessage() {
		
	//check if the connection is established with the peer realm.
	//If not exit.
	boolean isRealmAvailable = false;
	isRealmAvailable = myStack.isRealmAvailable(peerRealm,"myDiameterApp");
	if (isRealmAvailable == false) {
		System.out.println("peer realm "+peerRealm+" and 
		application myDiameterApp unavailable. Exiting.");
		cleanUpAndExit();
	}
				
// Message creation code here (example of previous section)
			
	// create session
	DiameterSession clientSession = null;
	clientSession = myProvider.createClientDiameterSession(null);
			
	//send message
	System.out.println("\nClient sending message :\n"+request.toString() );
		clientSession.sendMessage(request);
}

The following code (processGetPasswordIncomingRequest, called within ProcessEvent() in DiamBaseServer.java ) creates and sends a getPassword answer message statefully:

void processGetPasswordIncomingRequest (DiameterMessage request,
DiameterSession session) {

// create answer message
DiameterMessageFactory messageFactory = myStack.getDiameterMessageFactory() ;
DiameterMessage answer = messageFactory.createMessage(false,"getPassword",
"myDiameterApp" );
// add mandatory AVP
{
	DiameterAVP avp = messageFactory.createOctetStringAVP
	("Destination-Realm","base",peerRealm);
	answer.add(avp);
}
//add vendor specific AVP: username and password
{
	DiameterOctetStringAVP usernameAvp = (DiameterOctetStringAVP) 
	request.find("Username","hp");
	String password = ((DiameterOctetStringAVP) 
	request.find("Username","hp")).getStringValue () + "_password";
	DiameterAVP avp = messageFactory.createOctetStringAVP
	("Password","hp",password);
	answer.add(usernameAvp);
	answer.add(avp);
}
			
//create the Diameter server session
	session = myProvider.createServerDiameterSession(request);

//send message
System.out.println("Server sending answer message :\n"+answer.toString() ) ;
session.sendMessage(answer);
		
//close session
session.delete();
}

Receiving Diameter messages

All incoming base protocol messages are delivered to the user application through the method DiameterListener.processEvent(). The actual Diameter message can be retrieved as in the following DiamBaseServer.java example:

public void processEvent(DiameterEvent event)
{
	// other Events managed here (see previous section example code)
 
	if (event instanceof DiameterMessageEvent) {
		// process incoming messages
		DiameterMessageEvent incomingMessageEvent = 
		(DiameterMessageEvent) event;
		//Extract the DiameterMessage from the DiameterMessageEvent
		DiameterMessage incomingMessage = 
		incomingMessageEvent.getDiameterMessage();
		System.out.println("Server received message :\n"
		+incomingMessage.toString() ) ;
		//if message isn't a request, ignore msg and exit from the 
		procedure (this is a server code only)
		if (incomingMessage.getRequestBit() == false) {
			System.out.println("Server receiving message that is 
			not a request. Message ignored.");
			return;
		}
				
		//retrieve session, command and application names from 
		incoming message
		DiameterSession session = 
		incomingMessageEvent.getDiameterSession();
		String commandName = incomingMessage.getCommandName();
		String applicationName = incomingMessage.getApplicationName();
				
		// if the message is the custom getPassword request, handle it 
		//.here
		if (commandName.contentEquals("getPassword") && 
		applicationName.contentEquals("myDiameterApp")) {
			System.out.println
			("Command: getPassword\nApplication: myDiameterApp");
			processGetPasswordIncomingRequest
			(incomingMessage, session);
			//This method, creating an answer to the incoming 
			//request,will be detailed in the next section
		} else {
			System.out.println("Unknown message type,ignored.") ;
		}
	}
}

If a DiameterSession object exists for the incoming message (that is, if it is part of a Diameter session that is being handled in stateful mode), then this object may be retrieved by calling getSession on the incoming message. We'll see how to do that in the next section.

Important note about the Diameter Java API threading model:
The DiameterListener::processEvent() call is synchronous: no other incoming message/event is received unless the previous call has returned. In order to optimize performance especially in the case of high-latency applications (with database access for instance), the application can pass incoming messages to a queue handled in a separated thread, and return.

Cleaning-up the application

In order to exit properly from a Diameter application, the Diameter objects must be deleted, as in the following example:

void cleanUpAndExit() {
  /* Final cleanup before exit */
  System.out.println("Cleaning up...\n");
  /*
   * We use a do/while loop because the iterator cannot be re-used
   * once deleteDiameterRoute() has been called. Otherwise, a
   * ConcurrentModification exception is thrown.
   */
  {
    Iterator it;
    do {
      it = myStack.getDiameterRoutes();
      if (it.hasNext()) {
        DiameterRoute route = (DiameterRoute) 
        it.next();
        myStack.deleteDiameterRoute(route);
        route = null;
      }
    } while (it.hasNext());
  }
  myStack.deleteDiameterProvider(myProvider);
  myShProvider = null;
  {
    Iterator it;
    do {
      it = myStack.getDiameterListeningPoints();

The Diameter application “myDiameterApp” is implemented!

For more details, you can have a look at the Java source files available here: DiamBaseClient.java, DiamBaseServer.java