├── version ├── remove-source-references.sh ├── README.bindist ├── README.doc ├── README.misc ├── dk └── i1 │ └── diameter │ ├── node │ ├── DefaultConnectionListener.java │ ├── InvalidAVPValueException.java │ ├── DefaultMessageDispatcher.java │ ├── StaleConnectionException.java │ ├── AVP_FailedAVP.java │ ├── NotAnAnswerException.java │ ├── InvalidSettingException.java │ ├── UnsupportedURIException.java │ ├── NotProxiableException.java │ ├── NotARequestException.java │ ├── ConnectionTimeoutException.java │ ├── EmptyHostNameException.java │ ├── NotRoutableException.java │ ├── RelevantSCTPAuthInfo.java │ ├── UnsupportedTransportProtocolException.java │ ├── package.html │ ├── ConnectionListener.java │ ├── NormalConnectionBuffers.java │ ├── ConnectionKey.java │ ├── NodeState.java │ ├── DefaultNodeValidator.java │ ├── ConnectionBuffers.java │ ├── MessageDispatcher.java │ ├── Connection.java │ ├── TCPConnection.java │ ├── SCTPConnection.java │ ├── NodeImplementation.java │ ├── NodeValidator.java │ ├── SimpleSyncClient.java │ ├── ConnectionTimers.java │ ├── Capability.java │ ├── Peer.java │ ├── NodeSettings.java │ └── TCPNode.java │ ├── session │ ├── InvalidStateException.java │ ├── package.html │ ├── Session.java │ ├── SessionAuthTimers.java │ ├── AASession.java │ ├── SessionManager.java │ └── ACHandler.java │ ├── InvalidAVPLengthException.java │ ├── InvalidAddressTypeException.java │ ├── AVP_OctetString.java │ ├── VendorIDs.java │ ├── AVP_Integer32.java │ ├── AVP_Integer64.java │ ├── AVP_UTF8String.java │ ├── AVP_Unsigned32.java │ ├── AVP_Unsigned64.java │ ├── package.html │ ├── AVP_Float32.java │ ├── AVP_Float64.java │ ├── AVP_Time.java │ ├── packunpack.java │ ├── AVP_Grouped.java │ ├── AVP_Address.java │ ├── MessageHeader.java │ ├── AVP.java │ └── Message.java ├── README.src ├── README.sctp ├── lazy.txt ├── LICENSE ├── log.properties ├── stylesheet.css ├── examples ├── TestSessionTest.java ├── load │ ├── TestSessionServer.java │ └── TestSessionTest2.java ├── asr │ └── asr.java ├── TestSession.java ├── cc │ ├── cc_test_client.java │ └── cc_test_server.java └── relay │ └── simple_relay.java ├── overview.html ├── README.markdown ├── Makefile ├── Changes └── abnf └── ABNFConverter.java /version: -------------------------------------------------------------------------------- 1 | 0.9.6.13 2 | -------------------------------------------------------------------------------- /remove-source-references.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sed -r -e 's/()([^ ]*)(<\/A>)/\2/g' <$1 >$1.tmp 3 | touch -r $1.tmp $1 4 | mv -f $1.tmp $1 5 | -------------------------------------------------------------------------------- /README.bindist: -------------------------------------------------------------------------------- 1 | This package contains the dk.i1.diameter.* packages. 2 | You can see the version in the accompanying 'version' file. 3 | 4 | Please read the LICENSE file before using. 5 | -------------------------------------------------------------------------------- /README.doc: -------------------------------------------------------------------------------- 1 | This package contains documentation for the dk.i1.diameter.* packages. 2 | You can see the version in the accompanying 'version' file. 3 | 4 | Please read the LICENSE file before using. 5 | -------------------------------------------------------------------------------- /README.misc: -------------------------------------------------------------------------------- 1 | This package contains examples and tools for the dk.i1.diameter.* packages. 2 | You can see the version in the accompanying 'version' file. 3 | 4 | Please read the LICENSE file before using. 5 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/DefaultConnectionListener.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | class DefaultConnectionListener implements ConnectionListener { 4 | public void handle(ConnectionKey connkey, Peer peer, boolean up) { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/InvalidAVPValueException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import dk.i1.diameter.AVP; 3 | 4 | class InvalidAVPValueException extends Exception { 5 | public AVP avp; 6 | public InvalidAVPValueException(AVP avp) { 7 | this.avp=avp; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/DefaultMessageDispatcher.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import dk.i1.diameter.Message; 3 | 4 | class DefaultMessageDispatcher implements MessageDispatcher { 5 | public boolean handle(Message msg, ConnectionKey connkey, Peer peer) { 6 | return false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.src: -------------------------------------------------------------------------------- 1 | This package contains source code for the dk.i1.diameter.* packages. 2 | You can see the version in the accompanying 'version' file. 3 | 4 | The build process (Makefile) requires the JavaSCTP package, but you can build 5 | without it if you comment out the right lines in the makefile. 6 | 7 | Please read the LICENSE file before using. 8 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/StaleConnectionException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A reference to a closed connection was detected. 5 | * This exception is thrown when Node detects a reference to a closed connection. 6 | */ 7 | public class StaleConnectionException extends Exception { 8 | public StaleConnectionException() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/InvalidStateException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.session; 2 | 3 | public class InvalidStateException extends Exception { 4 | public InvalidStateException() { 5 | } 6 | public InvalidStateException(String message) { 7 | super(message); 8 | } 9 | public InvalidStateException(Throwable cause) { 10 | super(cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/AVP_FailedAVP.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | import dk.i1.diameter.*; 4 | 5 | class AVP_FailedAVP extends AVP_Grouped { 6 | private static AVP[] wrap(AVP a) { 7 | AVP g[] = new AVP[1]; 8 | g[0] = a; 9 | return g; 10 | } 11 | public AVP_FailedAVP(AVP a) { 12 | super(ProtocolConstants.DI_FAILED_AVP,wrap(a)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.sctp: -------------------------------------------------------------------------------- 1 | How to enable SCTP in the stack: 2 | 3 | 1: Get the Java SCTP packet from http://i1.dk/JavaSCTP 4 | 2: Make it 5 | 3: (linux only) modprobe sctp 6 | 4: Run your application with: 7 | java ... -Djava.library.path= ... 8 | Alternatively you can set te LD_LIBRARY_PATH environment variable before 9 | starting your program. 10 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NotAnAnswerException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A message was not an answer 5 | * This exception is thrown when trying to send a Message with a sendAnswer() or forwardAnswer() 6 | * method but the message was marked as a request. 7 | */ 8 | public class NotAnAnswerException extends Exception { 9 | public NotAnAnswerException() { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lazy.txt: -------------------------------------------------------------------------------- 1 | java -cp .:examples/cc -Djava.util.logging.config.file=log.properties cc_test_server isjsys.int.i1.dk example.net 2 | java -cp .:examples/cc -Djava.util.logging.config.file=log.properties cc_test_client foo.boo.goo boo.org isjsys.int.i1.dk 3868 3 | java -cp .:examples/relay -Djava.util.logging.config.file=log.properties simple_relay 42 isjsys-i0.int.i1.dk example.net isjsys-i1.int.i1.dk i1.dk 4 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/InvalidSettingException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * Invalid NodeSettings exception. 5 | * This exception is thrown when {@link NodeSettings} or 6 | * {@link dk.i1.diameter.session.SessionManager} detects an invalid setting. 7 | */ 8 | public class InvalidSettingException extends Exception { 9 | public InvalidSettingException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/UnsupportedURIException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * Thrown when giving {@link Peer#Peer(URI)} or {@link Peer#fromURIString(String)} an unsupported URI. 5 | */ 6 | public class UnsupportedURIException extends Exception { 7 | public UnsupportedURIException(String message) { 8 | super(message); 9 | } 10 | public UnsupportedURIException(Throwable cause) { 11 | super(cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NotProxiableException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A message was not proxiable. 5 | * This exception is thrown when forwarding a request or an answer 6 | * but the message was not marked as proxiable. 7 | * You probably forgot to check {@link dk.i1.diameter.MessageHeader#isProxiable}. 8 | */ 9 | public class NotProxiableException extends Exception { 10 | public NotProxiableException() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NotARequestException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A message was not a request. 5 | * This exception is thrown when trying to send a Message with a sendRequest() 6 | * method but the message was not marked as a request. 7 | * You probably forgot {@link dk.i1.diameter.MessageHeader#setRequest}. 8 | */ 9 | public class NotARequestException extends Exception { 10 | public NotARequestException() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/ConnectionTimeoutException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * ConnectionTimeout exception. 5 | * This exception is thrown when {@link dk.i1.diameter.node.Node#waitForConnectionTimeout} or 6 | * {@link dk.i1.diameter.node.NodeManager#waitForConnectionTimeout} times out. 7 | */ 8 | public class ConnectionTimeoutException extends java.util.concurrent.TimeoutException { 9 | public ConnectionTimeoutException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/EmptyHostNameException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A peer hostname was empty. 5 | * This exception is thrown when trying to construct a {@link Peer} with an 6 | * empty hostname. 7 | */ 8 | public class EmptyHostNameException extends Exception { 9 | public EmptyHostNameException() { 10 | } 11 | public EmptyHostNameException(String message) { 12 | super(message); 13 | } 14 | public EmptyHostNameException(Throwable cause) { 15 | super(cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dk/i1/diameter/InvalidAVPLengthException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * Exception thrown when an AVP does not have the correct size. 5 | */ 6 | public class InvalidAVPLengthException extends Exception { 7 | /**The AVP that did not have the correct size of its expected type. This can later be wrapped into an Failed-AVP AVP.*/ 8 | public AVP avp; 9 | /**Construct the expection with the specified AVP*/ 10 | public InvalidAVPLengthException(AVP avp) { 11 | this.avp = new AVP(avp); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dk/i1/diameter/InvalidAddressTypeException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * Exception thrown when an AVP_Address is constructed from unsupported on-the-wire content. 5 | */ 6 | public class InvalidAddressTypeException extends Exception { 7 | /**The AVP that did not have the correct size/type of its expected type. This can later be wrapped into an Failed-AVP AVP.*/ 8 | public AVP avp; 9 | /**Construct the expection with the specified AVP*/ 10 | public InvalidAddressTypeException(AVP avp) { 11 | this.avp = new AVP(avp); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NotRoutableException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A message was not routable. 5 | * This exception is thrown when NodeManager could not send a request either 6 | * because no connection(s) was available or because no available peers 7 | * supported the message. 8 | */ 9 | public class NotRoutableException extends Exception { 10 | public NotRoutableException() { 11 | } 12 | public NotRoutableException(String message) { 13 | super(message); 14 | } 15 | public NotRoutableException(Throwable cause) { 16 | super(cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/RelevantSCTPAuthInfo.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import dk.i1.sctp.SCTPSocket; 3 | import dk.i1.sctp.AssociationId; 4 | 5 | /** 6 | Peer authentication information (SCTP). 7 | Instances of this class is used for passing information to {@link NodeValidator}. 8 | @since 0.9.5 9 | */ 10 | public class RelevantSCTPAuthInfo { 11 | public SCTPSocket sctp_socket; 12 | public AssociationId assoc_id; 13 | RelevantSCTPAuthInfo(SCTPSocket sctp_socket, AssociationId assoc_id) { 14 | this.sctp_socket = sctp_socket; 15 | this.assoc_id = assoc_id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/UnsupportedTransportProtocolException.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * Unsupported transport protocol exception. 5 | * This exception is thrown when {@link Node#start} is called and one of the 6 | * mandatory transport protocols are not supported. 7 | */ 8 | public class UnsupportedTransportProtocolException extends Exception { 9 | public UnsupportedTransportProtocolException(String message) { 10 | super(message); 11 | } 12 | public UnsupportedTransportProtocolException(String message, Throwable ex) { 13 | super(message,ex); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_OctetString.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * AVP containing arbitrary data of variable length. 5 | */ 6 | public class AVP_OctetString extends AVP { 7 | public AVP_OctetString(AVP avp) { 8 | super(avp); 9 | } 10 | public AVP_OctetString(int code, byte value[]) { 11 | super(code,value); 12 | } 13 | public AVP_OctetString(int code, int vendor_id, byte value[]) { 14 | super(code,vendor_id,value); 15 | } 16 | public byte[] queryValue() { 17 | return queryPayload(); 18 | } 19 | public void setValue(byte value[]) { 20 | setPayload(value, 0, value.length); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/package.html: -------------------------------------------------------------------------------- 1 | 2 | Diameter node classes. 3 | This package contains classes for dealing with and implementing Diameter nodes. 4 | 5 |

How to create a node:

6 |
    7 |
  1. The first step in creating a Diameter Node is creating a set of capabilities. This is later on used for negotiating with peers. The capability set includes supported applications and vendor-IDs.
  2. 8 |
  3. The next step is creating a NodeSettings instance. A NodeSettings instance specifies the settings for your node including the capabilities, host-ID, etc.
  4. 9 |
  5. Then you are ready to create a Node, and NodeManager or SessionManager.
  6. 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /dk/i1/diameter/VendorIDs.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * A few vendor-ids that are useful. No attempt is made to make this list complete. 5 | * The complete list is at http://www.iana.org/assignments/enterprise-numbers and 6 | * @since 0.9.6.11 7 | */ 8 | public final class VendorIDs { 9 | 10 | static public final int Vendor_Cisco = 9; 11 | static public final int Vendor_Nokia = 94; 12 | static public final int Vendor_Ericsson = 193; 13 | static public final int Vendor_ALU = 637; 14 | static public final int Vendor_NTT_Docomo = 1408; 15 | static public final int Vendor_Huawei = 2011; 16 | static public final int Vendor_3GPP = 10415; 17 | static public final int Vendor_NSN = 28458; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/ConnectionListener.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A connection setup/tear-down observer. 5 | * The ConnectionListener interface is used by the {@link Node} class to 6 | * signal that a connection has been established (CER/CEA has been sucessfully 7 | * exchanged) or that a connection has been lost (Due to DPR or broken 8 | * transport connection) 9 | */ 10 | public interface ConnectionListener { 11 | /** 12 | * A connection has changed state. 13 | * If up is false then connkey is no longer valid (connection lost). 14 | * @param connkey The connection key. 15 | * @param peer The peer the connection is to. 16 | * @param up True if the connection has been established. False if the connection has been lost. 17 | */ 18 | public void handle(ConnectionKey connkey, Peer peer, boolean up); 19 | } 20 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Integer32.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * 32-bit signed integer AVP. 5 | */ 6 | public class AVP_Integer32 extends AVP { 7 | public AVP_Integer32(AVP a) throws InvalidAVPLengthException { 8 | super(a); 9 | if(a.queryPayloadSize()!=4) 10 | throw new InvalidAVPLengthException(a); 11 | } 12 | public AVP_Integer32(int code, int value) { 13 | super(code,int2byte(value)); 14 | } 15 | public AVP_Integer32(int code, int vendor_id, int value) { 16 | super(code,vendor_id,int2byte(value)); 17 | } 18 | public int queryValue() { 19 | return packunpack.unpack32(payload,0); 20 | } 21 | public void setValue(int value) { 22 | packunpack.pack32(payload,0,value); 23 | } 24 | 25 | static private final byte[] int2byte(int value) { 26 | byte[] v=new byte[4]; 27 | packunpack.pack32(v,0,value); 28 | return v; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Integer64.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * 64-bit signed integer AVP. 5 | */ 6 | public class AVP_Integer64 extends AVP { 7 | public AVP_Integer64(AVP a) throws InvalidAVPLengthException { 8 | super(a); 9 | if(a.queryPayloadSize()!=8) 10 | throw new InvalidAVPLengthException(a); 11 | } 12 | public AVP_Integer64(int code, long value) { 13 | super(code,long2byte(value)); 14 | } 15 | public AVP_Integer64(int code, int vendor_id, long value) { 16 | super(code,vendor_id,long2byte(value)); 17 | } 18 | public long queryValue() { 19 | return packunpack.unpack64(payload,0); 20 | } 21 | public void setValue(long value) { 22 | packunpack.pack64(payload,0,value); 23 | } 24 | static private final byte[] long2byte(long value) { 25 | byte[] v=new byte[8]; 26 | packunpack.pack64(v,0,value); 27 | return v; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2015 Ivan Skytte Jørgensen 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 8 | 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 11 | 3. This notice may not be removed or altered from any source distribution. 12 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NormalConnectionBuffers.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.nio.ByteBuffer; 3 | 4 | class NormalConnectionBuffers extends ConnectionBuffers { 5 | private ByteBuffer in_buffer; 6 | private ByteBuffer out_buffer; 7 | 8 | NormalConnectionBuffers() { 9 | in_buffer = ByteBuffer.allocate(8192); 10 | out_buffer = ByteBuffer.allocate(8192); 11 | } 12 | 13 | ByteBuffer netOutBuffer() { 14 | return out_buffer; 15 | } 16 | ByteBuffer netInBuffer() { 17 | return in_buffer; 18 | } 19 | ByteBuffer appInBuffer() { 20 | return in_buffer; 21 | } 22 | ByteBuffer appOutBuffer() { 23 | return out_buffer; 24 | } 25 | 26 | void processNetInBuffer() { 27 | } 28 | void processAppOutBuffer() { 29 | } 30 | 31 | void makeSpaceInNetInBuffer() { 32 | in_buffer = makeSpaceInBuffer(in_buffer,4096); 33 | } 34 | void makeSpaceInAppOutBuffer(int how_much) { 35 | out_buffer = makeSpaceInBuffer(out_buffer,how_much); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/ConnectionKey.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * A connection identifier. 5 | * ConnectionKey is used by {@link Node} to refer to a specific connection. 6 | * It can be used for remembering where a request came from then later send 7 | * an answer to it. If the connection has been lost in the meantime the Node 8 | * instance will not know that ConnectionKey and reject sending the message. 9 | * There is nothing interesting in the class except for equals() 10 | */ 11 | public class ConnectionKey { 12 | static private int i_seq=0; 13 | static final private synchronized int nextI() { 14 | return i_seq++; 15 | } 16 | private int i; 17 | public ConnectionKey() { 18 | i=nextI(); 19 | } 20 | public int hashCode() { return i; } 21 | public boolean equals(Object o) { 22 | if(this==o) 23 | return true; 24 | if(o==null || o.getClass()!=this.getClass()) 25 | return false; 26 | return ((ConnectionKey)o).i==i; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /log.properties: -------------------------------------------------------------------------------- 1 | # Specify the handlers to create in the root logger 2 | # (all loggers are children of the root logger) 3 | # The following creates two handlers 4 | handlers = java.util.logging.ConsoleHandler 5 | 6 | # Set the default logging level for the root logger 7 | .level = ALL 8 | 9 | # Set the default logging level for new ConsoleHandler instances 10 | java.util.logging.ConsoleHandler.level = ALL 11 | 12 | # Set the default logging level for new FileHandler instances 13 | #java.util.logging.FileHandler.level = ALL 14 | 15 | # Set the default formatter for new ConsoleHandler instances 16 | #java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter 17 | 18 | # Set the default logging level for the logger named com.mycompany 19 | #com.mycompany.level = ALL 20 | 21 | #dk.i1.diameter.node.level = INFO 22 | dk.i1.diameter.node.level = ALL 23 | dk.i1.diameter.session.level = INFO 24 | dk.i1.diameter.session.AASession.level = INFO 25 | TestSession.level = INFO 26 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_UTF8String.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.lang.reflect.Array; 3 | 4 | /** 5 | * AVP with UTF-8 string payload. 6 | */ 7 | public class AVP_UTF8String extends AVP { 8 | public AVP_UTF8String(AVP a) { 9 | super(a); 10 | } 11 | public AVP_UTF8String(int code, String value) { 12 | super(code,string2byte(value)); 13 | } 14 | public AVP_UTF8String(int code, int vendor_id, String value) { 15 | super(code,vendor_id,string2byte(value)); 16 | } 17 | public String queryValue() { 18 | try { 19 | return new String(queryPayload(),"UTF-8"); 20 | } catch(java.io.UnsupportedEncodingException e) { 21 | return null; 22 | } 23 | } 24 | public void setValue(String value) { 25 | setPayload(string2byte(value)); 26 | } 27 | 28 | static private final byte[] string2byte(String value) { 29 | try { 30 | return value.getBytes("UTF-8"); 31 | } catch(java.io.UnsupportedEncodingException e) { 32 | //never happens 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Unsigned32.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * 32-bit unsigned integer AVP. 5 | * RFC3855 describes the Unsigned32 AVP type. Java does not have an appropriate 6 | * unsigned data type, so this class is functionally equivalent to {@link AVP_Integer32} 7 | */ 8 | public class AVP_Unsigned32 extends AVP { 9 | public AVP_Unsigned32(AVP a) throws InvalidAVPLengthException { 10 | super(a); 11 | if(a.queryPayloadSize()!=4) 12 | throw new InvalidAVPLengthException(a); 13 | } 14 | public AVP_Unsigned32(int code, int value) { 15 | super(code,int2byte(value)); 16 | } 17 | public AVP_Unsigned32(int code, int vendor_id, int value) { 18 | super(code,vendor_id,int2byte(value)); 19 | } 20 | public int queryValue() { 21 | return packunpack.unpack32(payload,0); 22 | } 23 | public void setValue(int value) { 24 | packunpack.pack32(payload,0,value); 25 | } 26 | 27 | static private final byte[] int2byte(int value) { 28 | byte[] v=new byte[4]; 29 | packunpack.pack32(v,0,value); 30 | return v; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Unsigned64.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * 64-bit unsigned integer AVP. 5 | * RFC3855 describes the Unsigned64 AVP type. Java does not have an appropriate 6 | * unsigned data type, so this class is functionally equivalent to {@link AVP_Integer64} 7 | */ 8 | public class AVP_Unsigned64 extends AVP { 9 | public AVP_Unsigned64(AVP a) throws InvalidAVPLengthException { 10 | super(a); 11 | if(a.queryPayloadSize()!=8) 12 | throw new InvalidAVPLengthException(a); 13 | } 14 | public AVP_Unsigned64(int code, long value) { 15 | super(code,long2byte(value)); 16 | } 17 | public AVP_Unsigned64(int code, int vendor_id, long value) { 18 | super(code,vendor_id,long2byte(value)); 19 | } 20 | public long queryValue() { 21 | return packunpack.unpack64(payload,0); 22 | } 23 | public void setValue(long value) { 24 | packunpack.pack64(payload,0,value); 25 | } 26 | static private final byte[] long2byte(long value) { 27 | byte[] v=new byte[8]; 28 | packunpack.pack64(v,0,value); 29 | return v; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NodeState.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.util.Random; 3 | 4 | class NodeState { 5 | private final int state_id; 6 | private int end_to_end_identifier; 7 | private int session_id_high; 8 | private long session_id_low; //long because we need 32 unsigned bits 9 | 10 | NodeState() { 11 | int now = (int)(System.currentTimeMillis()/1000); 12 | state_id = now; 13 | end_to_end_identifier = (now<<20) | (new Random().nextInt() & 0x000FFFFF); 14 | session_id_high = now; 15 | session_id_low = 0; 16 | } 17 | 18 | public int stateId() { 19 | return state_id; 20 | } 21 | 22 | public synchronized int nextEndToEndIdentifier() { 23 | int v = end_to_end_identifier; 24 | end_to_end_identifier++; 25 | return v; 26 | } 27 | 28 | synchronized String nextSessionId_second_part() { 29 | long l = session_id_low; 30 | int h = session_id_high; 31 | session_id_low++; 32 | if(session_id_low==4294967296L) { 33 | session_id_low=0; 34 | session_id_high++; 35 | } 36 | return h + ";" + l; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dk/i1/diameter/package.html: -------------------------------------------------------------------------------- 1 | 2 | Diameter Messages and AVPs. 3 | This package contains classes for low-level dealing with Diameter messages and AVPs. 4 |

5 | The Message class contains a MessageHeader and a bunch of AVPs. The AVP classes are modelled after RFC3588. See {@link dk.i1.diameter.Message} to see examples of how to build and process messages. 6 |

7 | ProtocolConstants contains symbolic constants from the RFCs. 8 |

9 | Utils contains some utilities that a useful, particular for setting the M-bit on AVPs. 10 | 11 | 12 |

13 | Note: The following AVP types described in RFC358 have not been implemented: 14 |

15 |
DiameterIdentity
16 |
Basically a AVP_UTF8String. The RFC says that it is the FQDN of a node, but violations of this have been seen in the real world.
17 |
DiameterURI
18 |
A URI with certain rules. Not seen in the real world (yet)
19 |
Enumerated
20 |
Use AVP_Unsigned32 instead
21 |
IPFilterRule
22 |
QoSFilterRule
23 |
Presumable specific to NASREQ
24 |
25 | 26 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/package.html: -------------------------------------------------------------------------------- 1 | 2 | Classes for implementing a session-capable Diameter client. 3 | This package contains classes for implementing a Diameter node whose primary role is a client that holds diameter sessions, eg. a NAS, GGSN. 4 |

5 | This is done by using two classes. 6 | First, you instantiate a SessionManager. It takes care of the details about managing the node, sending and receiving messages (and lost connections), and timers in the sessions. 7 | Second you will create a class that implements the Session interface. It is best to make this a subclass of BaseSession og AASession. You will probably want to mix in the ACHandler to support accounting. 8 | 9 |

Incomplete example

10 |
11 | Capability cap = ... //see {@link dk.i1.diameter.node.Capability}
12 | NodeSettings settings = ... //see {@link dk.i1.diameter.node.NodeSettings}
13 | Peer peers[]= {
14 |     new Peer("somehost.example.net"),
15 |     new Peer("someotherhost.example.net"),
16 | };
17 | SessionManager session_manager = new SessionManager(settings,peers);
18 | 
19 | BaseSession session = new ExampleSession(...);
20 | 
21 | session.openSession();
22 | 
23 | 24 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Float32.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.nio.ByteBuffer; 3 | import java.nio.ByteOrder; 4 | 5 | /** 6 | * 32-bit floating point AVP 7 | */ 8 | public class AVP_Float32 extends AVP { 9 | public AVP_Float32(AVP a) throws InvalidAVPLengthException { 10 | super(a); 11 | if(a.queryPayloadSize()!=4) 12 | throw new InvalidAVPLengthException(a); 13 | } 14 | 15 | public AVP_Float32(int code, float value) { 16 | super(code,float2byte(value)); 17 | } 18 | public AVP_Float32(int code, int vendor_id, float value) { 19 | super(code,vendor_id,float2byte(value)); 20 | } 21 | 22 | public void setValue(float value) { 23 | setPayload(float2byte(value)); 24 | } 25 | 26 | public float queryValue() { 27 | byte v[] = queryPayload(); 28 | ByteBuffer bb = ByteBuffer.allocate(4); 29 | bb.order(ByteOrder.BIG_ENDIAN); 30 | bb.put(v); 31 | bb.rewind(); 32 | return bb.getFloat(); 33 | } 34 | 35 | static private final byte[] float2byte(float value) { 36 | ByteBuffer bb = ByteBuffer.allocate(4); 37 | bb.order(ByteOrder.BIG_ENDIAN); 38 | bb.putFloat(value); 39 | bb.rewind(); 40 | byte v[] = new byte[4]; 41 | bb.get(v); 42 | return v; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Float64.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.nio.ByteBuffer; 3 | import java.nio.ByteOrder; 4 | 5 | /** 6 | * 64-bit floating point AVP 7 | */ 8 | public class AVP_Float64 extends AVP { 9 | public AVP_Float64(AVP a) throws InvalidAVPLengthException { 10 | super(a); 11 | if(a.queryPayloadSize()!=4) 12 | throw new InvalidAVPLengthException(a); 13 | } 14 | 15 | public AVP_Float64(int code, double value) { 16 | super(code,double2byte(value)); 17 | } 18 | public AVP_Float64(int code, int vendor_id, double value) { 19 | super(code,vendor_id,double2byte(value)); 20 | } 21 | 22 | public void setValue(double value) { 23 | setPayload(double2byte(value)); 24 | } 25 | 26 | public double queryValue() { 27 | byte v[] = queryPayload(); 28 | ByteBuffer bb = ByteBuffer.allocate(4); 29 | bb.order(ByteOrder.BIG_ENDIAN); 30 | bb.put(v); 31 | bb.rewind(); 32 | return bb.getDouble(); 33 | } 34 | 35 | static private final byte[] double2byte(double value) { 36 | ByteBuffer bb = ByteBuffer.allocate(4); 37 | bb.order(ByteOrder.BIG_ENDIAN); 38 | bb.putDouble(value); 39 | bb.rewind(); 40 | byte v[] = new byte[4]; 41 | bb.get(v); 42 | return v; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | /* Javadoc style sheet */ 2 | 3 | /* Define colors, fonts and other style attributes here to override the defaults */ 4 | 5 | /* Page background color */ 6 | body { background-color: #FFFFFF } 7 | 8 | /* Table colors */ 9 | .TableHeadingColor { background: #CCCCFF } /* Dark mauve */ 10 | .TableSubHeadingColor { background: #EEEEFF } /* Light mauve */ 11 | .TableRowColor { background: #FFFFFF } /* White */ 12 | 13 | /* Font used in left-hand frame lists */ 14 | .FrameTitleFont { font-family: Helvetica, Arial, sans-serif } 15 | .FrameHeadingFont { font-family: Helvetica, Arial, sans-serif } 16 | .FrameItemFont { font-family: Helvetica, Arial, sans-serif } 17 | 18 | /* Navigation bar fonts and colors */ 19 | .NavBarCell1 { background-color:#EEEEFF;} /* Light mauve */ 20 | .NavBarCell1Rev { background-color:#00008B;} /* Dark Blue */ 21 | .NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;} 22 | .NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;} 23 | 24 | .NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;} 25 | .NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;} 26 | 27 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/DefaultNodeValidator.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | 3 | /** 4 | * Default node validator. 5 | * This node validator knows all nodes and always allows any capabilities. 6 | * It does this by not implementing any policy at all, blindly trusting peers 7 | * that they are who they claim to be and they are allowed to process the 8 | * applications they announce. 9 | * @since 0.9.4 10 | */ 11 | public class DefaultNodeValidator implements NodeValidator { 12 | /** 13 | * "authenticates" peer. 14 | * Always claims to know any peer. 15 | */ 16 | public AuthenticationResult authenticateNode(String node_id, Object obj) { 17 | AuthenticationResult ar = new AuthenticationResult(); 18 | ar.known = true; 19 | return ar; 20 | } 21 | /** 22 | * "authorizes" the capabilities claimed by a peer. 23 | * This implementation returns the simple intersection of the peers reported capabilities and our own capabilities. 24 | * Implemented as return Capability.calculateIntersection(settings.capabilities(), reported_capabilities); 25 | */ 26 | public Capability authorizeNode(String node_id, NodeSettings settings, Capability reported_capabilities) { 27 | Capability result_capabilities = Capability.calculateIntersection(settings.capabilities(), reported_capabilities); 28 | return result_capabilities; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/ConnectionBuffers.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.nio.ByteBuffer; 3 | //import java.nio.channels.SocketChannel; 4 | 5 | /** 6 | * 7 | */ 8 | abstract class ConnectionBuffers { 9 | abstract ByteBuffer netOutBuffer(); 10 | abstract ByteBuffer netInBuffer(); 11 | abstract ByteBuffer appInBuffer(); 12 | abstract ByteBuffer appOutBuffer(); 13 | abstract void processNetInBuffer(); 14 | abstract void processAppOutBuffer(); 15 | 16 | abstract void makeSpaceInNetInBuffer(); 17 | abstract void makeSpaceInAppOutBuffer(int how_much); 18 | 19 | void consumeNetOutBuffer(int bytes) { 20 | consume(netOutBuffer(),bytes); 21 | } 22 | void consumeAppInBuffer(int bytes) { 23 | consume(appInBuffer(),bytes); 24 | } 25 | 26 | 27 | static ByteBuffer makeSpaceInBuffer(ByteBuffer bb, int how_much) { 28 | if(bb.position()+how_much > bb.capacity()) { 29 | int bytes = bb.position(); 30 | int new_capacity = bb.capacity()+how_much; 31 | new_capacity = new_capacity + (4096-(new_capacity%4096)); 32 | ByteBuffer tmp = ByteBuffer.allocate(new_capacity); 33 | bb.flip(); 34 | tmp.put(bb); 35 | tmp.position(bytes); 36 | bb = tmp; 37 | } 38 | return bb; 39 | } 40 | static private void consume(ByteBuffer bb, int bytes) { 41 | bb.limit(bb.position()); 42 | bb.position(bytes); 43 | bb.compact(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/MessageDispatcher.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import dk.i1.diameter.Message; 3 | 4 | /** 5 | * A incoming message dispatcher. 6 | * A MessageDispatcher is used by the {@link Node} class to dispatch incoming messages. 7 | * Low-level house-keeping Diameter messages (CEx/DPx/DWx) are not dispatched 8 | * to it but instead handled by the Node directly. 9 | *

10 | * Please note that the handle() method is called by the networking thread and 11 | * messages from other peers cannot be received until the method returns. If 12 | * the handle() method needs to do any lengthy processing then it should 13 | * implement a message queue, put the message into the queue, and return. 14 | *

15 | * Also note that CER/CEA, DWR/DWA and DPR/DPA messages are given to the 16 | * dispatcher because the node handles them itself. STR/STA, ASR/ASA and 17 | * other base message are given to the dispatcher. 18 | */ 19 | public interface MessageDispatcher { 20 | /** 21 | *This method is called when the Node has received a message. 22 | *@param msg The incoming message 23 | *@param connkey The connection key 24 | *@param peer The peer of the connection. This is not necessarily the host that originated the message (the message can have gone via proxies) 25 | *@return True if the message was processed. False otherwise, in which case the Node will respond with a error to the peer (if the message was a request). 26 | */ 27 | public boolean handle(Message msg, ConnectionKey connkey, Peer peer); 28 | } 29 | -------------------------------------------------------------------------------- /examples/TestSessionTest.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | import dk.i1.diameter.session.*; 4 | 5 | class TestSessionTest { 6 | public static final void main(String args[]) throws Exception { 7 | if(args.length!=1) { 8 | System.out.println("Usage: "); 9 | return; 10 | } 11 | 12 | Capability capability = new Capability(); 13 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 14 | capability.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 15 | 16 | NodeSettings node_settings; 17 | try { 18 | node_settings = new NodeSettings( 19 | "TestSessionTest.example.net", "example.net", 20 | 99999, //vendor-id 21 | capability, 22 | 9999, //3868, //port must be non-zero because we have sessions 23 | "dk.i1.diameter.session.SessionManager test", 0x01000001); 24 | } catch (InvalidSettingException e) { 25 | System.out.println(e.toString()); 26 | return; 27 | } 28 | 29 | Peer peers[] = new Peer[]{ 30 | new Peer(args[0]) 31 | }; 32 | 33 | 34 | SessionManager session_manager = new SessionManager(node_settings,peers); 35 | 36 | session_manager.start(); 37 | Thread.sleep(500); 38 | 39 | BaseSession session = new TestSession(ProtocolConstants.DIAMETER_APPLICATION_NASREQ, session_manager); 40 | 41 | session.openSession(); 42 | System.out.println("Session state: " + session.state()); 43 | 44 | Thread.sleep(100000); 45 | 46 | System.out.println("Session state: " + session.state()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Time.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.util.Date; 3 | 4 | /** 5 | * A timestamp AVP. 6 | * AVP_Time contains a second count since 1900. You can get the raw second count 7 | * using {@link AVP_Unsigned32#queryValue} but this class' methods queryDate() 8 | * and querySecondsSince1970() are probably more useful in your program. 9 | *

10 | * Diameter does not have any base AVPs (RFC3588) with finer granularity than 11 | * seconds. 12 | */ 13 | public class AVP_Time extends AVP_Unsigned32 { 14 | private static final int seconds_between_1900_and_1970 = ((70*365)+17)*86400; 15 | 16 | public AVP_Time(AVP a) throws InvalidAVPLengthException { 17 | super(a); 18 | } 19 | public AVP_Time(int code, Date value) { 20 | this(code,0,value); 21 | } 22 | public AVP_Time(int code, int vendor_id, Date value) { 23 | super(code, vendor_id, (int)(value.getTime()/1000+seconds_between_1900_and_1970)); 24 | } 25 | public AVP_Time(int code, int seconds_since_1970) { 26 | this(code,0,seconds_since_1970); 27 | } 28 | public AVP_Time(int code, int vendor_id, int seconds_since_1970) { 29 | super(code, vendor_id, seconds_since_1970+seconds_between_1900_and_1970); 30 | } 31 | public Date queryDate() { 32 | return new Date((super.queryValue()-seconds_between_1900_and_1970)*1000L); 33 | } 34 | public int querySecondsSince1970() { 35 | return super.queryValue()-seconds_between_1900_and_1970; 36 | } 37 | public void setValue(Date value) { 38 | super.setValue((int)(value.getTime()/1000+seconds_between_1900_and_1970)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/Connection.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.net.InetAddress; 3 | import java.util.Collection; 4 | 5 | abstract class Connection { 6 | NodeImplementation node_impl; 7 | public Peer peer; //initially null 8 | public String host_id; //always set, updated from CEA/CER 9 | public ConnectionTimers timers; 10 | public ConnectionKey key; 11 | private int hop_by_hop_identifier_seq; 12 | 13 | public enum State { 14 | connecting, 15 | connected_in, //connected, waiting for cer 16 | connected_out, //connected, waiting for cea 17 | tls, //CE completed, negotiating TLS 18 | ready, //ready 19 | closing, //DPR sent, waiting for DPA 20 | closed 21 | } 22 | public State state; 23 | 24 | public Connection(NodeImplementation node_impl, long watchdog_interval, long idle_timeout) { 25 | this.node_impl = node_impl; 26 | timers = new ConnectionTimers(watchdog_interval,idle_timeout); 27 | key = new ConnectionKey(); 28 | hop_by_hop_identifier_seq = new java.util.Random().nextInt(); 29 | state = State.connected_in; 30 | } 31 | 32 | public synchronized int nextHopByHopIdentifier() { 33 | return hop_by_hop_identifier_seq++; 34 | } 35 | 36 | abstract InetAddress toInetAddress(); //todo: eliminate 37 | 38 | abstract void sendMessage(byte[] raw); 39 | 40 | abstract Object getRelevantNodeAuthInfo(); 41 | 42 | abstract Collection getLocalAddresses(); 43 | 44 | abstract Peer toPeer(); 45 | 46 | long watchdogInterval() { 47 | return timers.cfg_watchdog_timer; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /overview.html: -------------------------------------------------------------------------------- 1 | 2 |

Java Diameter library

3 |

4 | These 3 packages allows you to implement a various kinds of Diameter nodes. 5 | Since you are reading this, the documentation assumes that you already have 6 | a good idea of what the Diameter protocol is. 7 |

8 | Some places in this documentation there are references to RFC3855. If you think 9 | the documentation for that method/class is vague then it probably means that 10 | there are tricky issues or dependencies, and reading the referenced section in 11 | RFC3588 is a good idea. 12 |

13 | Where to start in this documentation? This probably depends on what it is that 14 | you are implementing. If you are creating a client then a good place to start 15 | is {@link dk.i1.diameter.node.SimpleSyncClient} or 16 | {@link dk.i1.diameter.session}. If you are implementing a proxy or back-end 17 | server then {@link dk.i1.diameter.node} is probably a good place to start. 18 |

Some technical details

19 |

RFC3588 compliance

20 |

21 | In general, most of RFC3588 is supported. IPv4 and IPv6 is supported. TLS encryption is not supported (yet). TCP transport is supported. SCTP transport is supported with the optional package Java SCTP. 22 |

Performance

23 |

24 | The performance is quite good and measured in thousands of messages per second. Your application and hardware will differ, and the best you can do is to run the load test located in examples/load. 25 |

26 | Ok, if you really want to know: you should experience about 8000-10000 messages per second on a 1.6GHz Opteron 27 | 28 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # JavaDiameter # 2 | 3 | _JavaDiameter_ is a library for supporting the diameter protocol. It contains raw protocol support, message and AVP encoders/decoders and various classes for implementing different diameter node types (proxies, servers, clients, ...). It is dictionary-less and schema-less. 4 | 5 | It is designed to be lean and mean with as few dependencies as possible. 6 | 7 | ## Dependencies ## 8 | One. My JavaSCTP library (available at http://i1.dk/JavaSCTP/). You can disable that dependency by editing the makefile. 9 | 10 | ## A few notes on the source code ## 11 | I'm not a java programmer so some constructs and fewer abstraction layers are not what you normally see in java. I like to think that the result is smaller and faster than what a normaly java programmer would produce. 12 | 13 | In order to keep the dependencies down it uses java.util.logging which is what was available in java 1.5. I have considered changing it but it seems to me that the java community can't make up their mind, and adding additional layers (eg. SLF4J) is neither going to make it faster nor have fewer dependencies. Quite the opposite. 14 | 15 | There is a branch _tls_ that supports TLS as per RFC3868. It doesn't do the CN<->node-identity validation, but it works. It doesn't support the new-style TLS as per the newer RFC6733. 16 | 17 | The source uses tabs (=8 spaces). Deal with it. 18 | 19 | ## History ## 20 | I made it as a proof-of-concept in 2005 and also to have a realistic project for learning java. The previous license was closed-source and restrictive in order to avoid conflicts of interest with my then-current employer. This is no longer a problem, so it is using the permissive zlib/png-style license. 21 | 22 | I no longer have the time to maintain it, but I did find time to put it up on github. Pull-requests are welcome but my reaction may not be timely. 23 | -------------------------------------------------------------------------------- /dk/i1/diameter/packunpack.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | class packunpack { 4 | public static final void pack8(byte b[], int offset, byte value) { 5 | b[offset] = value; 6 | } 7 | public static final void pack16(byte b[], int offset, int value) { 8 | b[offset+0] = (byte)((value>> 8)&0xFF); 9 | b[offset+1] = (byte)((value )&0xFF); 10 | } 11 | public static final void pack32(byte b[], int offset, int value) { 12 | b[offset+0] = (byte)((value>>24)&0xFF); 13 | b[offset+1] = (byte)((value>>16)&0xFF); 14 | b[offset+2] = (byte)((value>> 8)&0xFF); 15 | b[offset+3] = (byte)((value )&0xFF); 16 | } 17 | public static final void pack64(byte b[], int offset, long value) { 18 | b[offset+0] = (byte)((value>>56)&0xFF); 19 | b[offset+1] = (byte)((value>>48)&0xFF); 20 | b[offset+2] = (byte)((value>>40)&0xFF); 21 | b[offset+3] = (byte)((value>>32)&0xFF); 22 | b[offset+4] = (byte)((value>>24)&0xFF); 23 | b[offset+5] = (byte)((value>>16)&0xFF); 24 | b[offset+6] = (byte)((value>> 8)&0xFF); 25 | b[offset+7] = (byte)((value )&0xFF); 26 | } 27 | 28 | public static final byte unpack8(byte b[], int offset) { 29 | return b[offset]; 30 | } 31 | public static final int unpack32(byte b[], int offset) { 32 | return ((b[offset+0]&0xFF)<<24) 33 | | ((b[offset+1]&0xFF)<<16) 34 | | ((b[offset+2]&0xFF)<< 8) 35 | | ((b[offset+3]&0xFF) ) 36 | ; 37 | } 38 | public static final int unpack16(byte b[], int offset) { 39 | return ((b[offset+0]&0xFF)<< 8) 40 | | ((b[offset+1]&0xFF) ) 41 | ; 42 | } 43 | public static final long unpack64(byte b[], int offset) { 44 | return ((long)(b[offset+0]&0xFF)<<56) 45 | | ((long)(b[offset+1]&0xFF)<<48) 46 | | ((long)(b[offset+2]&0xFF)<<40) 47 | | ((long)(b[offset+3]&0xFF)<<32) 48 | | ((long)(b[offset+4]&0xFF)<<24) 49 | | ((long)(b[offset+5]&0xFF)<<16) 50 | | ((long)(b[offset+6]&0xFF)<< 8) 51 | | ((long)(b[offset+7]&0xFF) ) 52 | ; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/TCPConnection.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.nio.ByteBuffer; 3 | import java.nio.channels.SocketChannel; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.util.Collection; 7 | import java.util.ArrayList; 8 | 9 | class TCPConnection extends Connection { 10 | TCPNode node_impl; 11 | SocketChannel channel; 12 | ConnectionBuffers connection_buffers; 13 | 14 | public TCPConnection(TCPNode node_impl, long watchdog_interval, long idle_timeout) { 15 | super(node_impl,watchdog_interval,idle_timeout); 16 | this.node_impl = node_impl; 17 | connection_buffers = new NormalConnectionBuffers(); 18 | } 19 | 20 | void makeSpaceInNetInBuffer() { 21 | connection_buffers.makeSpaceInNetInBuffer(); 22 | } 23 | void makeSpaceInAppOutBuffer(int how_much) { 24 | connection_buffers.makeSpaceInAppOutBuffer(how_much); 25 | } 26 | void consumeAppInBuffer(int bytes) { 27 | connection_buffers.consumeAppInBuffer(bytes); 28 | } 29 | void consumeNetOutBuffer(int bytes) { 30 | connection_buffers.consumeNetOutBuffer(bytes); 31 | } 32 | boolean hasNetOutput() { 33 | return connection_buffers.netOutBuffer().position()!=0; 34 | } 35 | 36 | void processNetInBuffer() { 37 | connection_buffers.processNetInBuffer(); 38 | } 39 | void processAppOutBuffer() { 40 | connection_buffers.processAppOutBuffer(); 41 | } 42 | 43 | InetAddress toInetAddress() { 44 | return ((InetSocketAddress)(channel.socket().getRemoteSocketAddress())).getAddress(); 45 | } 46 | 47 | void sendMessage(byte[] raw) { 48 | node_impl.sendMessage(this,raw); 49 | } 50 | 51 | Object getRelevantNodeAuthInfo() { 52 | return channel; 53 | } 54 | 55 | Collection getLocalAddresses() { 56 | Collection coll = new ArrayList(); 57 | coll.add(channel.socket().getLocalAddress()); 58 | return coll; 59 | } 60 | 61 | Peer toPeer() { 62 | return new Peer(toInetAddress(),channel.socket().getPort()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/SCTPConnection.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.net.InetAddress; 3 | import java.net.InetSocketAddress; 4 | import java.util.Collection; 5 | import java.util.ArrayList; 6 | import dk.i1.sctp.*; 7 | import java.util.LinkedList; 8 | 9 | class SCTPConnection extends Connection { 10 | //Queue of pending messages 11 | private LinkedList queued_messages; 12 | private SCTPNode node_impl; 13 | AssociationId assoc_id; 14 | boolean closed; 15 | short sac_inbound_streams; 16 | short sac_outbound_streams; 17 | short out_stream_index; 18 | SCTPConnection(SCTPNode node_impl, long watchdog_interval, long idle_timeout) { 19 | super(node_impl,watchdog_interval,idle_timeout); 20 | queued_messages = new LinkedList(); 21 | this.node_impl = node_impl; 22 | this.closed = false; 23 | this.sac_inbound_streams = 0; 24 | this.sac_outbound_streams = 0; 25 | this.out_stream_index = 0; 26 | } 27 | 28 | //Return the next stream number to use for sending 29 | short nextOutStream() { 30 | short i = out_stream_index; 31 | out_stream_index = (short)((out_stream_index+1)%sac_outbound_streams); 32 | return i; 33 | } 34 | 35 | InetAddress toInetAddress() { 36 | Collection coll = getLocalAddresses(); 37 | for(InetAddress ia : coll) 38 | return ia; 39 | return null; 40 | } 41 | 42 | void sendMessage(byte[] raw) { 43 | node_impl.sendMessage(this,raw); 44 | } 45 | 46 | Object getRelevantNodeAuthInfo() { 47 | return new RelevantSCTPAuthInfo(node_impl.sctp_socket,assoc_id); 48 | } 49 | 50 | Collection getLocalAddresses() { 51 | try { 52 | return node_impl.sctp_socket.getLocalInetAddresses(assoc_id); 53 | } catch(java.net.SocketException ex) { 54 | return null; 55 | } 56 | } 57 | 58 | Peer toPeer() { 59 | try { 60 | return new Peer(toInetAddress(),node_impl.sctp_socket.getPeerInetPort(assoc_id)); 61 | } catch(java.net.SocketException ex) { 62 | return null; 63 | } 64 | } 65 | 66 | void queueMessage(byte[] raw) { 67 | queued_messages.addLast(raw); 68 | } 69 | byte[] peekFirstQueuedMessage() { 70 | return queued_messages.peek(); 71 | } 72 | void removeFirstQueuedMessage() { 73 | queued_messages.poll(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NodeImplementation.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.util.logging.Logger; 3 | import dk.i1.diameter.Message; 4 | 5 | /** 6 | * Base class for transport-specific implementations. 7 | * This class acts a a common superclass for the TCPNode and SCTPNode classes. 8 | */ 9 | abstract class NodeImplementation { 10 | private Node node; 11 | protected NodeSettings settings; 12 | protected Logger logger; 13 | NodeImplementation(Node node, NodeSettings settings, Logger logger) { 14 | this.node = node; 15 | this.settings = settings; 16 | this.logger = logger; 17 | } 18 | abstract void openIO() throws java.io.IOException; 19 | abstract void start(); 20 | abstract void wakeup(); 21 | abstract void initiateStop(long shutdown_deadline); 22 | abstract void join(); 23 | abstract void closeIO(); 24 | abstract boolean initiateConnection(Connection conn, Peer peer); 25 | abstract void close(Connection conn, boolean reset); 26 | abstract Connection newConnection(long watchdog_interval, long idle_timeout); 27 | 28 | //Helper functions. Mostly forwarders to node 29 | boolean anyOpenConnections() { 30 | return node.anyOpenConnections(this); 31 | } 32 | void registerInboundConnection(Connection conn) { 33 | node.registerInboundConnection(conn); 34 | } 35 | void unregisterConnection(Connection conn) { 36 | node.unregisterConnection(conn); 37 | } 38 | long calcNextTimeout() { 39 | return node.calcNextTimeout(this); 40 | } 41 | void closeConnection(Connection conn) { 42 | node.closeConnection(conn); 43 | } 44 | void closeConnection(Connection conn, boolean reset) { 45 | node.closeConnection(conn,reset); 46 | } 47 | boolean handleMessage(Message msg, Connection conn) { 48 | return node.handleMessage(msg,conn); 49 | } 50 | void runTimers() { 51 | node.runTimers(this); 52 | } 53 | void logRawDecodedPacket(byte[] raw, int offset, int msg_size) { 54 | node.logRawDecodedPacket(raw,offset,msg_size); 55 | } 56 | void logGarbagePacket(Connection conn, byte[] raw, int offset, int msg_size) { 57 | node.logGarbagePacket(conn,raw,offset,msg_size); 58 | } 59 | Object getLockObject() { 60 | return node.getLockObject(); 61 | } 62 | void initiateCER(Connection conn) { 63 | node.initiateCER(conn); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/load/TestSessionServer.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | import dk.i1.diameter.session.*; 4 | import java.util.*; 5 | 6 | class TestSessionServer extends NodeManager { 7 | TestSessionServer(NodeSettings node_settings) { 8 | super(node_settings); 9 | } 10 | 11 | public static final void main(String args[]) throws Exception { 12 | if(args.length!=1) { 13 | System.out.println("Usage: "); 14 | return; 15 | } 16 | Capability capability = new Capability(); 17 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 18 | capability.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 19 | 20 | NodeSettings node_settings; 21 | try { 22 | node_settings = new NodeSettings( 23 | args[0], "example.net", 24 | 99999, //vendor-id 25 | capability, 26 | 3868, 27 | "TestSessionServer", 0x01000000); 28 | } catch (InvalidSettingException e) { 29 | System.out.println(e.toString()); 30 | return; 31 | } 32 | 33 | TestSessionServer tss = new TestSessionServer(node_settings); 34 | tss.start(); 35 | 36 | System.out.println("Hit enter to terminate server"); 37 | System.in.read(); 38 | 39 | tss.stop(); 40 | } 41 | 42 | protected void handleRequest(dk.i1.diameter.Message request, ConnectionKey connkey, Peer peer) { 43 | //this is not the way to do it, but fine for a lean-and-mean test server 44 | Message answer = new Message(); 45 | answer.prepareResponse(request); 46 | AVP avp_session_id = request.find(ProtocolConstants.DI_SESSION_ID); 47 | if(avp_session_id!=null) 48 | answer.add(avp_session_id); 49 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE,ProtocolConstants.DIAMETER_RESULT_SUCCESS)); 50 | AVP avp_auth_app_id = request.find(ProtocolConstants.DI_AUTH_APPLICATION_ID); 51 | if(avp_auth_app_id!=null) 52 | answer.add(avp_auth_app_id); 53 | 54 | switch(request.hdr.command_code) { 55 | case ProtocolConstants.DIAMETER_COMMAND_AA: 56 | //answer.add(new AVP_Unsigned32(ProtocolConstants.DI_AUTHORIZATION_LIFETIME,60)); 57 | break; 58 | } 59 | 60 | Utils.setMandatory_RFC3588(answer); 61 | 62 | try { 63 | answer(answer,connkey); 64 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) { } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/Session.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.session; 2 | import dk.i1.diameter.Message; 3 | 4 | /** 5 | * The session interface is what the {@link SessionManager} operates on 6 | * @see BaseSession 7 | */ 8 | public interface Session { 9 | /** 10 | * sessionId() is called by the SessionManager (and other classes) to 11 | * obtain the Diameter Session-Id of the session. The BaseSession 12 | * class implements this by following RFC3588 section 8.8 13 | * @return The stable, eternally unique session-id of the session 14 | */ 15 | public String sessionId(); 16 | 17 | /** 18 | * This method is called when the SessionManager has received a request 19 | * for this session. 20 | * @param request The Diameter request for this session. 21 | * @return the Diameter result-code (RFC3588 section 7.1) 22 | */ 23 | public int handleRequest(Message request); 24 | 25 | /** 26 | * This method is called when the SessionManager has received an answer 27 | * regarding this session. 28 | * @param answer The Diameter answer for this session. 29 | * @param state The state specified in the {@link SessionManager#sendRequest} call. 30 | */ 31 | public void handleAnswer(Message answer, Object state); 32 | 33 | /** 34 | * This method is called when the SessionManager did not receive an 35 | * answer. 36 | * @param command_code The command_code in the original request. 37 | * @param state The state specified in the {@link SessionManager#sendRequest} call. 38 | */ 39 | public void handleNonAnswer(int command_code, Object state); 40 | 41 | /** 42 | * Calculate the next timeout for this session, if any. This method is 43 | * called by the SessionManager at appropriate times in order to 44 | * calculate when handleTimeouts() should be called. 45 | * @return Next absolute timeout in milliseconds. Long.MAX_VAUE if none. 46 | */ 47 | public long calcNextTimeout(); 48 | 49 | /** 50 | * Handle timeouts, if any. 51 | * This method is called by the Sessionmanager when it thinks that a 52 | * timeout has expired for the session. The session can take any 53 | * action it deems appropriate. The method may be called when no 54 | * timeouts has expired so the implementation should not get upset 55 | * about that. 56 | */ 57 | public void handleTimeout(); 58 | } 59 | -------------------------------------------------------------------------------- /examples/load/TestSessionTest2.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | import dk.i1.diameter.session.*; 4 | 5 | /** 6 | * Generate load by runing through a lot of sessions. 7 | * It is meant to be used with the TestSessionServer. 8 | */ 9 | class TestSessionTest2 { 10 | static int sessions_actually_opened=0; 11 | public static final void main(String args[]) throws Exception { 12 | if(args.length!=4) { 13 | System.out.println("Usage: "); 14 | return; 15 | } 16 | 17 | String peer = args[0]; 18 | int sessions = Integer.parseInt(args[1]); 19 | double rate = Double.parseDouble(args[2]); 20 | final int session_duration = Integer.parseInt(args[3]); 21 | 22 | Capability capability = new Capability(); 23 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 24 | capability.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ); 25 | 26 | NodeSettings node_settings; 27 | try { 28 | node_settings = new NodeSettings( 29 | "TestSessionTest2.example.net", "example.net", 30 | 99999, //vendor-id 31 | capability, 32 | 9999, 33 | "TestSessionTest2", 0x01000000); 34 | } catch (InvalidSettingException e) { 35 | System.out.println(e.toString()); 36 | return; 37 | } 38 | 39 | Peer peers[] = new Peer[]{ 40 | new Peer(peer) 41 | //new Peer(peer,3868,Peer.TransportProtocol.sctp) 42 | }; 43 | 44 | 45 | SessionManager session_manager = new SessionManager(node_settings,peers); 46 | 47 | session_manager.start(); 48 | Thread.sleep(2000); //allow connections to be established. 49 | 50 | for(int i = 0; i!=sessions; i++) { 51 | TestSession ts = new TestSession(ProtocolConstants.DIAMETER_APPLICATION_NASREQ,session_manager) { 52 | public void newStatePost(State prev_state, State new_state, Message msg, int cause) { 53 | if(new_state==State.open) { 54 | updateSessionTimeout(session_duration); 55 | sessions_actually_opened++; 56 | } 57 | super.newStatePost(prev_state,new_state,msg,cause); 58 | } 59 | }; 60 | ts.openSession(); 61 | Thread.sleep((long)(1000/rate)); 62 | } 63 | 64 | Thread.sleep(session_duration*1000+50); 65 | 66 | System.out.println("sessions_actually_opened="+sessions_actually_opened); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Grouped.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * AVP grouping multiple AVPs together. 5 | * The following example shows how to construct a grouped AVP. 6 | *

 7 | Message ccr = ...;
 8 | ccr.add(new AVP_Grouped(ProtocolConstants.DI_VENDOR_SPECIFIC_APPLICATION_ID,
 9 |                         new AVP[] {
10 |                             new AVP_Unsigned32(ProtocolConstants.DI_VENDOR_ID, my_vendor_id).setM(),
11 |                             new AVP_Unsigned32(ProtocolConstants.DI_ACCT_APPLICATION_ID, my_application_id).setM()
12 |                         }
13 |                        ).setM()
14 |        );
15 | 
16 | */ 17 | public class AVP_Grouped extends AVP { 18 | public AVP_Grouped(AVP a) throws InvalidAVPLengthException { 19 | super(a); 20 | 21 | int offset=0; 22 | byte raw[] = queryPayload(); 23 | int i=0; 24 | while(offsetraw.length) 33 | throw new InvalidAVPLengthException(a); 34 | } 35 | public AVP_Grouped(int code, AVP... g) { 36 | super(code,avps2byte(g)); 37 | } 38 | public AVP_Grouped(int code, int vendor_id, AVP... g) { 39 | super(code,vendor_id,avps2byte(g)); 40 | } 41 | 42 | public AVP[] queryAVPs() { 43 | int offset=0; 44 | byte raw[] = queryPayload(); 45 | int i=0; 46 | while(offset "); 12 | return; 13 | } 14 | 15 | String peer = args[0]; 16 | int auth_app_id; 17 | if(args[1].equals("nasreq")) 18 | auth_app_id=ProtocolConstants.DIAMETER_APPLICATION_NASREQ; 19 | else if(args[1].equals("mobileip")) 20 | auth_app_id=ProtocolConstants.DIAMETER_APPLICATION_MOBILEIP; 21 | else 22 | auth_app_id = Integer.valueOf(args[1]); 23 | String session_id = args[2]; 24 | String dest_host = args[0]; 25 | String dest_realm = dest_host.substring(dest_host.indexOf('.')+1); 26 | 27 | Capability capability = new Capability(); 28 | capability.addAuthApp(auth_app_id); 29 | 30 | NodeSettings node_settings; 31 | try { 32 | node_settings = new NodeSettings( 33 | "somehost.example.net", "example.net", 34 | 99999, //vendor-id 35 | capability, 36 | 9999, 37 | "ASR client", 0x01000000); 38 | } catch (InvalidSettingException e) { 39 | System.out.println(e.toString()); 40 | return; 41 | } 42 | 43 | Peer peers[] = new Peer[]{ 44 | new Peer(peer) 45 | }; 46 | 47 | SimpleSyncClient ssc = new SimpleSyncClient(node_settings,peers); 48 | ssc.start(); 49 | Thread.sleep(2000); //allow connections to be established. 50 | 51 | //Build ASR 52 | Message asr = new Message(); 53 | asr.add(new AVP_UTF8String(ProtocolConstants.DI_SESSION_ID,session_id)); 54 | ssc.node().addOurHostAndRealm(asr); 55 | asr.add(new AVP_UTF8String(ProtocolConstants.DI_DESTINATION_REALM,dest_realm)); 56 | asr.add(new AVP_UTF8String(ProtocolConstants.DI_DESTINATION_HOST,dest_host)); 57 | asr.add(new AVP_Unsigned32(ProtocolConstants.DI_AUTH_APPLICATION_ID,auth_app_id)); 58 | Utils.setMandatory_RFC3588(asr); 59 | 60 | //Send it 61 | Message asa = ssc.sendRequest(asr); 62 | if(asa==null) { 63 | System.out.println("No response"); 64 | return; 65 | } 66 | 67 | //look at result-code 68 | AVP avp_result_code = asa.find(ProtocolConstants.DI_RESULT_CODE); 69 | if(avp_result_code==null) { 70 | System.out.println("No result-code in response (?)"); 71 | return; 72 | } 73 | int result_code = new AVP_Unsigned32(avp_result_code).queryValue(); 74 | if(result_code!=ProtocolConstants.DIAMETER_RESULT_SUCCESS) { 75 | System.out.println("Result-code was not success"); 76 | return; 77 | } 78 | 79 | ssc.stop(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/SessionAuthTimers.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.session; 2 | 3 | /** 4 | * Authorization time calculator. 5 | * This utility class keeps track of the authorization-lifetime and 6 | * authorization-grace-period and calculates when the session must be close or 7 | * when a re-authorization must be sent. 8 | */ 9 | public class SessionAuthTimers { 10 | private long latest_auth_time; //absolute, milliseconds 11 | private long next_reauth_time; //absolute, milliseconds 12 | private long auth_timeout; //absolute, milliseconds 13 | 14 | /** 15 | * Updates the calculations based on the supplied values. The method 16 | * will try to schedule the re-authorization (if any) 10 seconds before 17 | * the session would have to be closed otherwise. 18 | * @param auth_time The time then the authorization succeeded 19 | * (absolute, milliseconds). 20 | * Ideally, this should be the time when the 21 | * users is given service (for the first 22 | * authorization), and the server's time when 23 | * the re-authorization succeeds. In most cases 24 | * System.currentTimeMillis() will do. 25 | * @param auth_lifetime The granted authorization lifetime in 26 | * relative milliseconds. Use 0 to specify no 27 | * authorization-lifetime. 28 | * @param auth_grace_period The authorization-grace-period in relative 29 | * milliseconds. Use 0 to specify none. 30 | */ 31 | public void updateTimers(long auth_time, long auth_lifetime, long auth_grace_period) { 32 | latest_auth_time = auth_time; 33 | if(auth_lifetime!=0) { 34 | auth_timeout = latest_auth_time + auth_lifetime + auth_grace_period; 35 | if(auth_grace_period!=0) { 36 | next_reauth_time = latest_auth_time + auth_lifetime; 37 | } else { 38 | //schedule reauth to 10 seconds before timeout. Should be plenty for carrier-grade servers. 39 | next_reauth_time = Math.max(auth_time+auth_lifetime/2, auth_timeout-10); 40 | } 41 | } else { 42 | next_reauth_time = Long.MAX_VALUE; 43 | auth_timeout = Long.MAX_VALUE; 44 | } 45 | } 46 | 47 | /** 48 | * Retrieve the calculated time for the next re-authorization. 49 | * @return The next re-authorization time, in milliseconds. Will be 50 | * Long.MAX_VALUE if there is none. 51 | */ 52 | public long getNextReauthTime() { 53 | return next_reauth_time; 54 | } 55 | /** 56 | * Retrieve the maximum timeout of the session after which service must 57 | * be denied and the session should be closed. 58 | * @return The timeout. Will be Long.MAX_VALUE if there is no timeout. 59 | */ 60 | public long getMaxTimeout() { 61 | return auth_timeout; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP_Address.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.net.InetAddress; 3 | import java.net.Inet4Address; 4 | import java.net.Inet6Address; 5 | 6 | /** 7 | * An internet address AVP. 8 | * This class reflects the Address type AVP described in RFC3588. 9 | * It supports both IPv4 and IPv6. 10 | *

Note:Values not conforming to RFC3588 has been seen in the wild. 11 | */ 12 | public class AVP_Address extends AVP_OctetString { 13 | public AVP_Address(AVP a) throws InvalidAVPLengthException, InvalidAddressTypeException { 14 | super(a); 15 | if(a.queryPayloadSize()<2) 16 | throw new InvalidAVPLengthException(a); 17 | int address_family = packunpack.unpack16(payload,0); 18 | if(address_family==1) { 19 | if(a.queryPayloadSize()!=2+4) 20 | throw new InvalidAVPLengthException(a); 21 | } else if(address_family==2) { 22 | if(a.queryPayloadSize()!=2+16) 23 | throw new InvalidAVPLengthException(a); 24 | } else 25 | throw new InvalidAddressTypeException(a); 26 | } 27 | public AVP_Address(int code, InetAddress ia) { 28 | super(code,InetAddress2byte(ia)); 29 | } 30 | public AVP_Address(int code, int vendor_id, InetAddress ia) { 31 | super(code,vendor_id,InetAddress2byte(ia)); 32 | } 33 | 34 | public InetAddress queryAddress() throws InvalidAVPLengthException, InvalidAddressTypeException { 35 | if(queryPayloadSize()<2) 36 | throw new InvalidAVPLengthException(this); 37 | byte raw[] = queryValue(); 38 | int address_family = packunpack.unpack16(raw,0); 39 | try { 40 | switch(address_family) { 41 | case 1: { 42 | if(queryPayloadSize()!=2+4) 43 | throw new InvalidAVPLengthException(this); 44 | byte tmp[] = new byte[4]; 45 | System.arraycopy(raw,2,tmp,0,4); 46 | return InetAddress.getByAddress(tmp); 47 | } 48 | case 2: { 49 | if(queryPayloadSize()!=2+16) 50 | throw new InvalidAVPLengthException(this); 51 | byte tmp[] = new byte[16]; 52 | System.arraycopy(raw,2,tmp,0,16); 53 | return InetAddress.getByAddress(tmp); 54 | } 55 | default: 56 | throw new InvalidAddressTypeException(this); 57 | 58 | } 59 | } catch(java.net.UnknownHostException e) { } 60 | return null; //never reached 61 | } 62 | 63 | public void setAddress(InetAddress ia) { 64 | setValue(InetAddress2byte(ia)); 65 | } 66 | 67 | static private final byte[] InetAddress2byte(InetAddress ia) { 68 | byte raw_address[] = ia.getAddress(); 69 | int address_family; 70 | try { 71 | Inet4Address i4a = (Inet4Address)(ia); 72 | address_family = 1; //http://www.iana.org/assignments/address-family-numbers 73 | } catch(ClassCastException e) { 74 | Inet6Address i6a = (Inet6Address)(ia); 75 | address_family = 2; 76 | } 77 | 78 | byte raw[] = new byte[2+raw_address.length]; 79 | packunpack.pack16(raw,0,address_family); 80 | System.arraycopy(raw_address,0, raw, 2, raw_address.length); 81 | return raw; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/TestSession.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.session.*; 3 | import java.util.*; 4 | import java.util.logging.Logger; 5 | import java.util.logging.Level; 6 | 7 | /** 8 | * A quite simple session based on AASession, and supports simple accounting. 9 | * This class is used by some of the other examples. 10 | */ 11 | public class TestSession extends AASession { 12 | static private Logger logger = Logger.getLogger("TestSession"); 13 | ACHandler achandler; 14 | public TestSession(int auth_app_id, SessionManager session_manager) { 15 | super(auth_app_id,session_manager); 16 | achandler = new ACHandler(this); 17 | achandler.acct_application_id = ProtocolConstants.DIAMETER_APPLICATION_NASREQ; 18 | } 19 | 20 | public void handleAnswer(Message answer, Object state) { 21 | logger.log(Level.FINE,"processing answer"); 22 | switch(answer.hdr.command_code) { 23 | case ProtocolConstants.DIAMETER_COMMAND_ACCOUNTING: 24 | achandler.handleACA(answer); 25 | break; 26 | default: 27 | super.handleAnswer(answer,state); 28 | break; 29 | } 30 | } 31 | 32 | public void handleNonAnswer(int command_code, Object state) { 33 | logger.log(Level.FINE,"processing non-answer"); 34 | switch(command_code) { 35 | case ProtocolConstants.DIAMETER_COMMAND_ACCOUNTING: 36 | achandler.handleACA(null); 37 | break; 38 | default: 39 | super.handleNonAnswer(command_code,state); 40 | break; 41 | } 42 | } 43 | 44 | protected void collectAARInfo(Message request) { 45 | super.collectAARInfo(request); 46 | request.add(new AVP_UTF8String(ProtocolConstants.DI_USER_NAME,"user@example.net")); 47 | } 48 | protected boolean processAAAInfo(Message answer) { 49 | try { 50 | Iterator it=answer.iterator(ProtocolConstants.DI_ACCT_INTERIM_INTERVAL); 51 | if(it.hasNext()) { 52 | int interim_interval = new AVP_Unsigned32(it.next()).queryValue(); 53 | if(interim_interval!=0) 54 | achandler.subSession(0).interim_interval = interim_interval*1000; 55 | } 56 | } catch(dk.i1.diameter.InvalidAVPLengthException e) { 57 | return false; 58 | } 59 | return super.processAAAInfo(answer); 60 | } 61 | 62 | public long calcNextTimeout() { 63 | long t = super.calcNextTimeout(); 64 | if(state()==State.open) 65 | t = Math.min(t,achandler.calcNextTimeout()); 66 | return t; 67 | } 68 | public void handleTimeout() { 69 | //update acct_session_time 70 | if(state()==State.open) 71 | achandler.subSession(0).acct_session_time = System.currentTimeMillis()-firstAuthTime(); 72 | //Then do the timeout handling 73 | super.handleTimeout(); 74 | achandler.handleTimeout(); 75 | } 76 | 77 | 78 | public void newStatePre(State prev_state, State new_state, Message msg, int cause) { 79 | logger.log(Level.FINE,"prev="+prev_state+" new="+new_state); 80 | if(prev_state!=State.discon && new_state==State.discon) 81 | achandler.stopSession(); 82 | } 83 | 84 | public void newStatePost(State prev_state, State new_state, Message msg, int cause) { 85 | logger.log(Level.FINE,"prev="+prev_state+" new="+new_state); 86 | if(prev_state!=State.open && new_state==State.open) 87 | achandler.startSession(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/SimpleSyncClient.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import dk.i1.diameter.Message; 3 | 4 | /** 5 | * A simple Diameter client that support synchronous request-answer calls. 6 | * It does not support receiving requests. 7 | */ 8 | public class SimpleSyncClient extends NodeManager { 9 | private Peer peers[]; 10 | 11 | /** 12 | * Constructor for SimpleSyncClient 13 | * @param settings The settings to use for this client 14 | * @param peers The upstream peers to use 15 | */ 16 | public SimpleSyncClient(NodeSettings settings, Peer peers[]) { 17 | super(settings); 18 | this.peers = peers; 19 | } 20 | 21 | /** 22 | * Starts this client. The client must be started before sending 23 | * requests. Connections to the configured upstream peers will be 24 | * initiated but this method may return before they have been 25 | * established. 26 | *@see NodeManager#waitForConnection 27 | */ 28 | public void start() throws java.io.IOException, UnsupportedTransportProtocolException { 29 | super.start(); 30 | for(Peer p : peers) { 31 | node().initiateConnection(p,true); 32 | } 33 | } 34 | 35 | private static class SyncCall { 36 | boolean answer_ready; 37 | Message answer; 38 | } 39 | 40 | /** 41 | * Dispatches an answer to threads waiting for it. 42 | */ 43 | protected void handleAnswer(Message answer, ConnectionKey answer_connkey, Object state) { 44 | SyncCall sc = (SyncCall)state; 45 | synchronized(sc) { 46 | sc.answer = answer; 47 | sc.answer_ready = true; 48 | sc.notify(); 49 | } 50 | } 51 | 52 | /** 53 | * Send a request and wait for an answer. 54 | * @param request The request to send 55 | * @return The answer to the request. Null if there is no answer (all peers down, or other error) 56 | */ 57 | public Message sendRequest(Message request) { 58 | return sendRequest(request,-1); 59 | } 60 | 61 | /** 62 | * Send a request and wait for an answer. 63 | * @param request The request to send 64 | * @param timeout Timeout in milliseconds. -1 means no timeout. 65 | * @return The answer to the request. Null if there is no answer (all peers down, timeout, or other error) 66 | * @since 0.9.6.8 timeout parameter introduced 67 | */ 68 | public Message sendRequest(Message request, long timeout) { 69 | SyncCall sc = new SyncCall(); 70 | sc.answer_ready = false; 71 | sc.answer=null; 72 | 73 | long timeout_time = System.currentTimeMillis() + timeout; 74 | 75 | try { 76 | sendRequest(request, peers, sc, timeout); 77 | //ok, sent 78 | synchronized(sc) { 79 | if(timeout>=0) { 80 | long now = System.currentTimeMillis(); 81 | long relative_timeout = timeout_time - now; 82 | if(relative_timeout>0) 83 | while(System.currentTimeMillis()= last_activity + watchdog_timer_with_jitter) 119 | { 120 | return timer_action.disconnect_no_cer; 121 | } 122 | return timer_action.none; 123 | } 124 | 125 | if(cfg_idle_close_timeout!=0) { 126 | if(now >= last_real_activity + cfg_idle_close_timeout) 127 | { 128 | return timer_action.disconnect_idle; 129 | } 130 | } 131 | 132 | //section 3.4.1 item 1 133 | if(now >= last_activity + watchdog_timer_with_jitter) { 134 | if(!dw_outstanding) { 135 | return timer_action.dwr; 136 | } else { 137 | if(now >= last_activity + cfg_watchdog_timer + cfg_watchdog_timer) { 138 | //section 3.4.1 item 3+4 139 | return timer_action.disconnect_no_dw; 140 | } 141 | } 142 | } 143 | return timer_action.none; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /dk/i1/diameter/MessageHeader.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * A Diameter message header. 5 | * See RFC3588 section 3. After you have read that understanding the class is trivial. 6 | * The only fields and methods you will normally use are: 7 | *

    8 | *
  • {@link #command_code}
  • 9 | *
  • {@link #application_id} (maybe)
  • 10 | *
  • {@link #setProxiable}
  • 11 | *
  • {@link #setRequest}
  • 12 | *
13 | * Note: The default command flags does not include the proxiable bit, meaning 14 | * that request messages by default cannot be proxied by diameter proxies and 15 | * other gateways. It is still not determined if this is a reasonable default. 16 | * You should always call setProxiable() explicitly so it has the value you 17 | * expect it to be. 18 | */ 19 | public class MessageHeader { 20 | byte version; 21 | //int message_length; 22 | private byte command_flags; 23 | public int command_code; 24 | public int application_id; 25 | public int hop_by_hop_identifier; 26 | public int end_to_end_identifier; 27 | 28 | public static final byte command_flag_request_bit = (byte)0x80; 29 | public static final byte command_flag_proxiable_bit = 0x40; 30 | public static final byte command_flag_error_bit = 0x20; 31 | public static final byte command_flag_retransmit_bit = 0x10; 32 | 33 | public boolean isRequest() { 34 | return (command_flags&command_flag_request_bit)!=0; 35 | } 36 | public boolean isProxiable() { 37 | return (command_flags&command_flag_proxiable_bit)!=0; 38 | } 39 | public boolean isError() { 40 | return (command_flags&command_flag_error_bit)!=0; 41 | } 42 | public boolean isRetransmit() { 43 | return (command_flags&command_flag_retransmit_bit)!=0; 44 | } 45 | public void setRequest(boolean b) { 46 | if(b) 47 | command_flags |= command_flag_request_bit; 48 | else 49 | command_flags &= ~command_flag_request_bit; 50 | } 51 | public void setProxiable(boolean b) { 52 | if(b) 53 | command_flags |= command_flag_proxiable_bit; 54 | else 55 | command_flags &= ~command_flag_proxiable_bit; 56 | } 57 | /**Set error bit. See RFC3588 section 3 page 32 before you set this.*/ 58 | public void setError(boolean b) { 59 | if(b) 60 | command_flags |= command_flag_error_bit; 61 | else 62 | command_flags &= ~command_flag_error_bit; 63 | } 64 | /**Set retransmit bit*/ 65 | public void setRetransmit(boolean b) { 66 | if(b) 67 | command_flags |= command_flag_retransmit_bit; 68 | else 69 | command_flags &= ~command_flag_retransmit_bit; 70 | } 71 | 72 | /** 73 | * Default constructor for MessageHeader. 74 | * The command flags are initialized to answer+not-proxiable+not-error+not-retransmit, also known as 0. 75 | * The command_code, application_id, hop_by_hop_identifier and end_to_end_identifier are initialized to 0. 76 | */ 77 | public MessageHeader() { 78 | version = 1; 79 | } 80 | 81 | /** 82 | * Copy-constructor for MessageHeader. 83 | * Implements a deep copy. 84 | */ 85 | public MessageHeader(MessageHeader mh) { 86 | version = mh.version; 87 | command_flags = mh.command_flags; 88 | command_code = mh.command_code; 89 | application_id = mh.application_id; 90 | hop_by_hop_identifier = mh.hop_by_hop_identifier; 91 | end_to_end_identifier = mh.end_to_end_identifier; 92 | } 93 | 94 | int encodeSize() { 95 | return 5*4; 96 | } 97 | int encode(byte b[], int offset, int message_length) { 98 | packunpack.pack32(b, offset+ 0, message_length); 99 | packunpack.pack8 (b, offset+ 0, version); 100 | packunpack.pack32(b, offset+ 4, command_code); 101 | packunpack.pack8 (b, offset+ 4, command_flags); 102 | packunpack.pack32(b, offset+ 8, application_id); 103 | packunpack.pack32(b, offset+12, hop_by_hop_identifier); 104 | packunpack.pack32(b, offset+16, end_to_end_identifier); 105 | return 5*4; 106 | } 107 | 108 | void decode(byte b[], int offset) { 109 | version = packunpack.unpack8(b, offset+0); 110 | //message_length = Array.getInt(b,offset+0)&0x00FFFFFF; 111 | command_flags = packunpack.unpack8(b,offset+4); 112 | command_code = packunpack.unpack32(b,offset+4)&0x00FFFFFF; 113 | application_id = packunpack.unpack32(b,offset+8); 114 | hop_by_hop_identifier = packunpack.unpack32(b,offset+12); 115 | end_to_end_identifier = packunpack.unpack32(b,offset+16); 116 | } 117 | 118 | /** 119 | * Prepare a response from the specified request header. 120 | * The proxiable flag is copied - the other flags are cleared. 121 | * The command_code, application_id, hop_by_hop_identifier, 122 | * end_to_end_identifier and 'proxyable' command flag 123 | * are copied. The 'request', 'error' and 'retransmit' bits are cleared. 124 | */ 125 | public void prepareResponse(MessageHeader request) { 126 | command_flags = (byte)(request.command_flags&command_flag_proxiable_bit); 127 | command_code = request.command_code; 128 | application_id = request.application_id; 129 | hop_by_hop_identifier = request.hop_by_hop_identifier; 130 | end_to_end_identifier = request.end_to_end_identifier; 131 | } 132 | 133 | /** 134 | * Prepare an answer from the specified request header. 135 | * This is identical to prepareResponse(). 136 | * @since 0.9.3 137 | */ 138 | public void prepareAnswer(MessageHeader request) { 139 | prepareResponse(request); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .java .class 2 | 3 | .java.class: 4 | # javac -g -cp .:examples $< 5 | javac -cp .:examples:../JavaSCTP/JavaSCTP.jar $< 6 | 7 | P=dk/i1/diameter 8 | CLASSES=$P/packunpack.class \ 9 | $P/AVP.class \ 10 | $P/AVP_Integer32.class \ 11 | $P/AVP_Unsigned32.class \ 12 | $P/AVP_Integer64.class \ 13 | $P/AVP_Unsigned64.class \ 14 | $P/AVP_OctetString.class \ 15 | $P/AVP_Float32.class \ 16 | $P/AVP_Float64.class \ 17 | $P/AVP_Grouped.class \ 18 | $P/AVP_UTF8String.class \ 19 | $P/AVP_Time.class \ 20 | $P/AVP_Address.class \ 21 | $P/InvalidAVPLengthException.class \ 22 | $P/InvalidAddressTypeException.class \ 23 | $P/MessageHeader.class \ 24 | $P/Message.class \ 25 | $P/ProtocolConstants.class \ 26 | $P/VendorIDs.class \ 27 | $P/Utils.class \ 28 | $P/node/Capability.class \ 29 | $P/node/UnsupportedURIException.class \ 30 | $P/node/EmptyHostNameException.class \ 31 | $P/node/NodeValidator.class \ 32 | $P/node/DefaultNodeValidator.class \ 33 | $P/node/Peer.class \ 34 | $P/node/MessageDispatcher.class \ 35 | $P/node/DefaultMessageDispatcher.class \ 36 | $P/node/ConnectionListener.class \ 37 | $P/node/DefaultConnectionListener.class \ 38 | $P/node/ConnectionKey.class \ 39 | $P/node/InvalidSettingException.class \ 40 | $P/node/StaleConnectionException.class \ 41 | $P/node/NodeSettings.class \ 42 | $P/node/ConnectionBuffers.class \ 43 | $P/node/NormalConnectionBuffers.class \ 44 | $P/node/ConnectionTimers.class \ 45 | $P/node/Connection.class \ 46 | $P/node/AVP_FailedAVP.class \ 47 | $P/node/InvalidAVPValueException.class \ 48 | $P/node/NotRoutableException.class \ 49 | $P/node/NotAnAnswerException.class \ 50 | $P/node/NotARequestException.class \ 51 | $P/node/NotProxiableException.class \ 52 | $P/node/NodeState.class \ 53 | $P/node/NodeImplementation.class \ 54 | $P/node/TCPConnection.class \ 55 | $P/node/TCPNode.class \ 56 | $P/node/RelevantSCTPAuthInfo.class \ 57 | $P/node/SCTPConnection.class \ 58 | $P/node/SCTPNode.class \ 59 | $P/node/UnsupportedTransportProtocolException.class \ 60 | $P/node/ConnectionTimeoutException.class \ 61 | $P/node/Node.class \ 62 | $P/node/NodeManager.class \ 63 | $P/node/SimpleSyncClient.class \ 64 | $P/session/Session.class \ 65 | $P/session/InvalidStateException.class \ 66 | $P/session/SessionManager.class \ 67 | $P/session/SessionAuthTimers.class \ 68 | $P/session/BaseSession.class \ 69 | $P/session/AASession.class \ 70 | $P/session/ACHandler.class \ 71 | examples/TestSession.class \ 72 | examples/TestSessionTest.class \ 73 | examples/load/TestSessionTest2.class \ 74 | examples/load/TestSessionServer.class \ 75 | examples/asr/asr.class \ 76 | examples/cc/cc_test_client.class \ 77 | examples/cc/cc_test_server.class \ 78 | examples/relay/simple_relay.class \ 79 | abnf/ABNFConverter.class \ 80 | 81 | .PHONY: all 82 | all: $(CLASSES) 83 | 84 | Diameter.jar: $(CLASSES) 85 | jar -cf $@ `find dk -name \*.class` 86 | 87 | .PHONY: clean 88 | clean: 89 | find . -name \*.class -exec rm {} \; 90 | rm -f Diameter.jar 91 | rm -rf doc/ 92 | 93 | .PHONY: doc 94 | doc: 95 | javadoc -sourcetab 8 \ 96 | -d ./doc \ 97 | -classpath ../JavaSCTP/JavaSCTP.jar:. \ 98 | -windowtitle "Java Diameter API" \ 99 | -notimestamp \ 100 | -stylesheetfile stylesheet.css \ 101 | -overview overview.html \ 102 | -noqualifier java.lang \ 103 | dk.i1.diameter dk.i1.diameter.node dk.i1.diameter.session 104 | .PHONY: fixdoc 105 | fixdoc: 106 | rm -rf doc/src-html 107 | find doc/dk -name \*.html -exec ./remove-source-references.sh {} \; 108 | 109 | BNAME=Diameter-$(shell cat version) 110 | .PHONY: bindist 111 | bindist: Diameter.jar 112 | ln -sf `basename $(shell pwd)` ../$(BNAME) 113 | cd .. && tar cvfz $(BNAME)/$(BNAME)-jars.tar.gz \ 114 | $(BNAME)/Diameter.jar \ 115 | $(BNAME)/version $(BNAME)/LICENSE \ 116 | $(BNAME)/README.sctp \ 117 | $(BNAME)/README.bindist 118 | rm -f ../$(BNAME) 119 | 120 | .PHONY: misc 121 | miscdist: 122 | ln -sf `basename $(shell pwd)` ../$(BNAME) 123 | cd .. && tar cvfz $(BNAME)/$(BNAME)-misc.tar.gz \ 124 | `find $(BNAME)/examples -name \*.java` \ 125 | `find $(BNAME)/abnf -name \*.java` \ 126 | $(BNAME)/version $(BNAME)/LICENSE \ 127 | $(BNAME)/README.misc 128 | rm -f ../$(BNAME) 129 | 130 | .PHONY: srcdist 131 | srcdist: 132 | ln -sf `basename $(shell pwd)` ../$(BNAME) 133 | cd .. && tar cvfz $(BNAME)/$(BNAME)-src.tar.gz \ 134 | $(BNAME)/Makefile \ 135 | `find $(BNAME)/dk -name \*.java` \ 136 | $(BNAME)/version $(BNAME)/LICENSE \ 137 | $(BNAME)/README.src 138 | rm -f ../$(BNAME) 139 | 140 | .PHONY: docdist 141 | docdist: doc fixdoc 142 | ln -sf `basename $(shell pwd)` ../$(BNAME) 143 | cd .. && tar cvfz $(BNAME)/$(BNAME)-doc.tar.gz \ 144 | $(BNAME)/doc \ 145 | $(BNAME)/version $(BNAME)/LICENSE \ 146 | $(BNAME)/README.doc 147 | rm -f ../$(BNAME) 148 | 149 | .PHONY: dist 150 | dist: bindist miscdist srcdist docdist 151 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Changes 0.9.6.12 -> 0.9.6.13 2 | * Changed license to a permissable zlib/png-style 3 | 4 | Changes 0.9.6.11 -> 0.9.6.12 5 | * NPE when server sent unmatched hop-by-hop-identifier (e.g. due to server bug or client timeout) 6 | 7 | Changes 0.9.6.10 -> 0.9.6.11 8 | * Handle 3GPP mismatch between announced applications in CER/CEA and actual 9 | messages. 10 | 11 | Changes 0.9.6.9 -> 0.9.6.10 12 | * Allow bogus PRNG when initializing DWR jitter 13 | * Eliminate (harmless) race condition when shutting down under high load 14 | leading to "WARNING: Got a DPA. This is not expected" 15 | * Mismatch between relative and absolute timeouts in request times. (should 16 | be relative) 17 | 18 | Changes 0.9.6.8 -> 0.9.6.9 19 | * Fixed IllegalMonitorStateException when shutting down 20 | 21 | Changes 0.9.6.7 -> 0.9.6.8 22 | * Added timeout capability to SimpleSyncClient and NodeManager 23 | * Logs destination IP address before initiating outgoing connections 24 | * Prevent double-start of stack (would lead to erratic behaviour) 25 | * Support TCP source port range 26 | 27 | Changes 0.9.6.6 -> 0.9.6.7 28 | * Expanded CC example server 29 | * Fixed npe on outgoing sctp connections that fail 30 | * Support replaced logger implementation, eg. in resin and other application 31 | servers. 32 | 33 | Changes 0.9.6.5 -> 0.9.6.6 34 | * AVP_Time.queryDate() returned wrong values de to 32-bit arithmic. 35 | 36 | Changes 0.9.6.4 -> 0.9.6.5 37 | * NodeManager could not find matching oustanding requests if the state object 38 | was null. 39 | * Node.waitForConnectionTimeout() and NodeManager.waitForConnectionTimeout() 40 | methods added. 41 | * Handle more than 2^31 sessions connectly when generating Session-IDs 42 | * Support some unusual vendor-specific-application-id AVP formats. 43 | * AVP_Time.queryDate() returned wrong value (Date object creation with seconds 44 | instead of milliseconds) 45 | * AVP.queryPayload() made public. 46 | 47 | Changes 0.9.6.3 -> 0.9.6.4 48 | * Fix regression in 0.9.6.3: 49 | - ConcurrentModificationException when talking to silent peer 50 | - Bugfix when connect() connects imediately. (solaris) 51 | Both regressions were due to my fat fingers and subversion happily 52 | accepting changes to tags. 53 | 54 | Changes 0.9.6.? -> 0.9.6.3 55 | * Made more robust when calling Node.findConnection/sendMessage/... when the 56 | node has not been started. 57 | 58 | Changes 0.9.6.1 -> 0.9.6.2 59 | * If a peer accepted the connection, but never replied to CER, then a 60 | ConcurrentModificationException was thrown when the connection timed out. 61 | * Local connections on Solaris lead to corner case where NIO behaves 62 | "interestingly" and the stack became non-functional. 63 | 64 | Changes 0.9.6 -> 0.9.6.1 65 | * File descriptor leak when a persistent peer had a name that could not be 66 | looked up in DNS. 67 | * DCC examples no longer announce credit-control application as a supported 68 | accounting application. 69 | 70 | Changes 0.9.5 -> 0.9.6 71 | * Handle SCTP association restart correctly. 72 | * Node.findConnection() could return connection keys for connections there were 73 | not in the "open" state, potentially leading to NPE if a client immediately 74 | tried using it. 75 | * Logging in Node changed a bit to make failure to load SCTP support look less 76 | dangerous. 77 | * Do not attempt to send DWR on non-ready connections. 78 | * Timeout&close non-ready connections. 79 | 80 | Changes 0.9.4 -> 0.9.5 81 | * SCTP support 82 | * CER election phase could easily fail. 83 | * Improved handling of M-bit in grouped AVPs in dk.i1.diameter.Utils 84 | * Implemented DW interval jitter as per rfc3539 section 3.4.1 item 1 85 | 86 | Changes 0.9.4 -> 0.9.4.1 87 | * M-bit was not set on AVPs inside Vendor-Specific-Application-Id 88 | 89 | Changes 0.9.3 -> 0.9.4 90 | * Examines result-code in CEA and acts on it. 91 | * Error-bit was not set in error responses for protocol errors. 92 | * Vendor-specific accounting applications in the CE messages were incorrectly 93 | sent as vendor-specific authentication applications. 94 | * Log vendor-specific applications during capability negotiation. 95 | * Implemented peer authentication (see NodeValidator interface). 96 | * CER with election-lost was missing the Result-Code AVP. 97 | 98 | Changes 0.9.2 -> 0.9.3 99 | * On shutdown peers were sent a DPR with disconnect-cause='busy' instead of the 100 | (correct) 'rebooting' cause. 101 | * It is now possible to have a (small) grace period for making a clean 102 | shutdown of connections waiting for reception of DPA. This has mostly 103 | academic interest. The default is to send DPR and close immediately. 104 | * An CEA was erroneously sent as response to an unhandled CEA 105 | * Idle timeout changed from 1 hour to 7 days. 106 | * NodeSettings.???watchdoginterval() and NodeSettings.???idleTimeout() added. 107 | * NPE when new inbound connection lost election 108 | * Support for connecting to relays 109 | * Connecting to peers on non-standard ports resulted in not being able to 110 | send messages to them due to port mismatch when looking up the peer. 111 | * Added note about restoring hop-by-hop-identifier when forwarding answers 112 | with NodeManager.forwardAnswer() 113 | * Added example relay to show per-message state handling in NodeManager. 114 | * Message.prepareAnswer() and Messageheader.prepareAnswer() was added. They 115 | are identical to prepareResponse() but more appropriately named. 116 | 117 | Changes 0.9.1 -> 0.9.2 118 | * Reset TCP connection when non-diameter traffic is encountered (RFC3588 119 | section 8). Previously, the connection was simply closed. 120 | * waitforConnection() was not working right. 121 | * Moved session-id generation to Node 122 | * Added credit-control example 123 | * Raw packets were not logged with log-level=FINEST 124 | * AVP.setM() method added 125 | * Shutting down a node could in some cases result in a NullPointerException 126 | in "Diameter node reconnect thread" 127 | 128 | Changes 0.9.0 -> 0.9.1 129 | * Node.waitForConnection() and NodeManager.waitForConnection() added. 130 | * AVP.vendor_id was not being reset on reused AVPs when calling decode() 131 | * Made dk.i1.diameter.Messageheader constructors public 132 | * With certain malformed origin-host-id values from peers the Node thread 133 | would encounter a NullPointerException because java.net.URI could not 134 | parse the URI. 135 | * Potential race condition fix with sending messages to a peer after it 136 | was marked ready but before a CEA was sent. 137 | * Faster detection of non-diameter traffic on sockets 138 | * CER, DWR, DPR always had the hop-by-hop-identifier set to 0. 139 | -------------------------------------------------------------------------------- /examples/cc/cc_test_client.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | 4 | /** 5 | * A simple client that issues a CCR (credit-control request) 6 | */ 7 | 8 | class cc_test_client { 9 | public static final void main(String args[]) throws Exception { 10 | if(args.length!=4) { 11 | System.out.println("Usage: "); 12 | return; 13 | } 14 | 15 | String host_id = args[0]; 16 | String realm = args[1]; 17 | String dest_host = args[2]; 18 | int dest_port = Integer.parseInt(args[3]); 19 | 20 | Capability capability = new Capability(); 21 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_CREDIT_CONTROL); 22 | //capability.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_CREDIT_CONTROL); 23 | 24 | NodeSettings node_settings; 25 | try { 26 | node_settings = new NodeSettings( 27 | host_id, realm, 28 | 99999, //vendor-id 29 | capability, 30 | 0, 31 | "cc_test_client", 0x01000000); 32 | } catch (InvalidSettingException e) { 33 | System.out.println(e.toString()); 34 | return; 35 | } 36 | 37 | Peer peers[] = new Peer[]{ 38 | new Peer(dest_host,dest_port) 39 | }; 40 | 41 | SimpleSyncClient ssc = new SimpleSyncClient(node_settings,peers); 42 | ssc.start(); 43 | ssc.waitForConnection(); //allow connection to be established. 44 | 45 | //Build Credit-Control Request 46 | // ::= < Diameter Header: 272, REQ, PXY > 47 | Message ccr = new Message(); 48 | ccr.hdr.command_code = ProtocolConstants.DIAMETER_COMMAND_CC; 49 | ccr.hdr.application_id = ProtocolConstants.DIAMETER_APPLICATION_CREDIT_CONTROL; 50 | ccr.hdr.setRequest(true); 51 | ccr.hdr.setProxiable(true); 52 | // < Session-Id > 53 | ccr.add(new AVP_UTF8String(ProtocolConstants.DI_SESSION_ID,ssc.node().makeNewSessionId())); 54 | // { Origin-Host } 55 | // { Origin-Realm } 56 | ssc.node().addOurHostAndRealm(ccr); 57 | // { Destination-Realm } 58 | ccr.add(new AVP_UTF8String(ProtocolConstants.DI_DESTINATION_REALM,"example.net")); 59 | // { Auth-Application-Id } 60 | ccr.add(new AVP_Unsigned32(ProtocolConstants.DI_AUTH_APPLICATION_ID,ProtocolConstants.DIAMETER_APPLICATION_CREDIT_CONTROL)); // a lie but a minor one 61 | // { Service-Context-Id } 62 | ccr.add(new AVP_UTF8String(ProtocolConstants.DI_SERVICE_CONTEXT_ID,"cc_test@example.net")); 63 | // { CC-Request-Type } 64 | ccr.add(new AVP_Unsigned32(ProtocolConstants.DI_CC_REQUEST_TYPE,ProtocolConstants.DI_CC_REQUEST_TYPE_EVENT_REQUEST));; 65 | // { CC-Request-Number } 66 | ccr.add(new AVP_Unsigned32(ProtocolConstants.DI_CC_REQUEST_NUMBER,0)); 67 | // [ Destination-Host ] 68 | // [ User-Name ] 69 | ccr.add(new AVP_UTF8String(ProtocolConstants.DI_USER_NAME,"user@example.net")); 70 | // [ CC-Sub-Session-Id ] 71 | // [ Acct-Multi-Session-Id ] 72 | // [ Origin-State-Id ] 73 | ccr.add(new AVP_Unsigned32(ProtocolConstants.DI_ORIGIN_STATE_ID,ssc.node().stateId())); 74 | // [ Event-Timestamp ] 75 | ccr.add(new AVP_Time(ProtocolConstants.DI_EVENT_TIMESTAMP,(int)(System.currentTimeMillis()/1000))); 76 | // *[ Subscription-Id ] 77 | // [ Service-Identifier ] 78 | // [ Termination-Cause ] 79 | // [ Requested-Service-Unit ] 80 | ccr.add(new AVP_Grouped(ProtocolConstants.DI_REQUESTED_SERVICE_UNIT, 81 | new AVP[] {new AVP_Unsigned64(ProtocolConstants.DI_CC_SERVICE_SPECIFIC_UNITS,42)} 82 | ) 83 | ); 84 | // [ Requested-Action ] 85 | ccr.add(new AVP_Unsigned32(ProtocolConstants.DI_REQUESTED_ACTION,ProtocolConstants.DI_REQUESTED_ACTION_DIRECT_DEBITING)); 86 | // *[ Used-Service-Unit ] 87 | // [ Multiple-Services-Indicator ] 88 | // *[ Multiple-Services-Credit-Control ] 89 | // *[ Service-Parameter-Info ] 90 | ccr.add(new AVP_Grouped(ProtocolConstants.DI_SERVICE_PARAMETER_INFO, 91 | new AVP[] {new AVP_Unsigned32(ProtocolConstants.DI_SERVICE_PARAMETER_TYPE,42), 92 | new AVP_UTF8String(ProtocolConstants.DI_SERVICE_PARAMETER_VALUE,"Hovercraft") 93 | } 94 | ) 95 | ); 96 | // [ CC-Correlation-Id ] 97 | // [ User-Equipment-Info ] 98 | // *[ Proxy-Info ] 99 | // *[ Route-Record ] 100 | // *[ AVP ] 101 | 102 | Utils.setMandatory_RFC3588(ccr); 103 | Utils.setMandatory_RFC4006(ccr); 104 | 105 | //Send it 106 | Message cca = ssc.sendRequest(ccr); 107 | 108 | //Now look at the result 109 | if(cca==null) { 110 | System.out.println("No response"); 111 | return; 112 | } 113 | AVP result_code = cca.find(ProtocolConstants.DI_RESULT_CODE); 114 | if(result_code==null) { 115 | System.out.println("No result code"); 116 | return; 117 | } 118 | try { 119 | AVP_Unsigned32 result_code_u32 = new AVP_Unsigned32(result_code); 120 | int rc = result_code_u32.queryValue(); 121 | switch(rc) { 122 | case ProtocolConstants.DIAMETER_RESULT_SUCCESS: 123 | System.out.println("Success"); 124 | break; 125 | case ProtocolConstants.DIAMETER_RESULT_END_USER_SERVICE_DENIED: 126 | System.out.println("End user service denied"); 127 | break; 128 | case ProtocolConstants.DIAMETER_RESULT_CREDIT_CONTROL_NOT_APPLICABLE: 129 | System.out.println("Credit-control not applicable"); 130 | break; 131 | case ProtocolConstants.DIAMETER_RESULT_CREDIT_LIMIT_REACHED: 132 | System.out.println("Credit-limit reached"); 133 | break; 134 | case ProtocolConstants.DIAMETER_RESULT_USER_UNKNOWN: 135 | System.out.println("User unknown"); 136 | break; 137 | case ProtocolConstants.DIAMETER_RESULT_RATING_FAILED: 138 | System.out.println("Rating failed"); 139 | break; 140 | default: 141 | //Some other error 142 | //There are too many to decode them all. 143 | //We just print the classification 144 | if(rc>=1000 && rc<1999) 145 | System.out.println("Informational: " + rc); 146 | else if(rc>=2000 && rc<2999) 147 | System.out.println("Success: " + rc); 148 | else if(rc>=3000 && rc<3999) 149 | System.out.println("Protocl error: " + rc); 150 | else if(rc>=4000 && rc<4999) 151 | System.out.println("Transient failure: " + rc); 152 | else if(rc>=5000 && rc<5999) 153 | System.out.println("Permanent failure: " + rc); 154 | else 155 | System.out.println("(unknown error class): " + rc); 156 | 157 | } 158 | } catch(InvalidAVPLengthException ex) { 159 | System.out.println("result-code was illformed"); 160 | return; 161 | } 162 | 163 | //Stop the stack 164 | ssc.stop(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /examples/relay/simple_relay.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | import java.util.*; 4 | 5 | /** 6 | * A simple diameter relay. 7 | * As start-up arguments it takes a bit of configuration including a list of 8 | * upstream diameter nodes. This example application shows how NodeManager can 9 | * handle state and keep track of forwarded requests. 10 | * 11 | * It does not have any realm-based routing, but instead simply forwards 12 | * requests to the first available upstream peer, so it is probably not 13 | * suitable for production use. 14 | */ 15 | class simple_relay extends NodeManager { 16 | private ArrayList upstream_peers; 17 | simple_relay(NodeSettings node_settings) { 18 | super(node_settings); 19 | upstream_peers = new ArrayList(); 20 | } 21 | 22 | private void rejectRequest(Message request, ConnectionKey connkey, int why) { 23 | Message answer = new Message(); 24 | answer.prepareResponse(request); 25 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE,why)); 26 | Utils.setMandatory_RFC3588(answer); 27 | try { 28 | answer(answer,connkey); 29 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) { } 30 | } 31 | 32 | private boolean isUpstreamPeer(Peer peer) { 33 | for(Peer p : upstream_peers) { 34 | if(p==peer) 35 | return true; 36 | if(p.equals(peer)) 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | private static final class ForwardedRequestState { 43 | ConnectionKey connkey; 44 | int hop_by_hop_identifier; 45 | ForwardedRequestState(ConnectionKey connkey, int hop_by_hop_identifier) { 46 | this.connkey=connkey; 47 | this.hop_by_hop_identifier=hop_by_hop_identifier; 48 | } 49 | } 50 | 51 | protected void handleRequest(Message request, ConnectionKey connkey, Peer peer) { 52 | //If destination-host is present we have to honour that. 53 | AVP avp_destination_host = request.find(ProtocolConstants.DI_DESTINATION_HOST); 54 | if(avp_destination_host!=null) { 55 | String destination_host = new AVP_UTF8String(avp_destination_host).queryValue(); 56 | //If it is ourselves... 57 | if(destination_host.equals(settings().hostId())) { 58 | //Since we do not hold any sessions or real state (we are a relay) we can simply reject it. 59 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_COMMAND_UNSUPPORTED); 60 | } else { 61 | //Not ourselves 62 | ConnectionKey ck=null; 63 | try { 64 | ck = node().findConnection(new Peer(destination_host)); 65 | } catch(EmptyHostNameException ex) { } 66 | if(ck!=null) { 67 | //Forward to peer 68 | try { 69 | forwardRequest(request,ck,new ForwardedRequestState(connkey,request.hdr.hop_by_hop_identifier)); 70 | } catch(StaleConnectionException ex) { 71 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_UNABLE_TO_DELIVER); 72 | } catch(NotARequestException ex) { 73 | //never happens 74 | } catch(NotProxiableException ex) { 75 | //Imbecile peer 76 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_UNABLE_TO_DELIVER); 77 | } 78 | } else { 79 | //The destination host could be behind 80 | //another relay/proxy, but we are a 81 | //relay therefore too stupid to do 82 | //intelligent routing. 83 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_UNABLE_TO_DELIVER); 84 | } 85 | } 86 | return; 87 | } 88 | 89 | if(isUpstreamPeer(peer)) { 90 | //If it was a request from an upstream host then we 91 | //reject it - it is supposed to have a destination-host 92 | //AVP in the request. 93 | //(we are a relay and therefore too stupid to do 94 | //realm-based routing) 95 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_UNABLE_TO_DELIVER); 96 | } else { 97 | //If it is a request from one of the non-upstream peers 98 | //we simply forward it to one of the upstream peers 99 | for(Peer p : upstream_peers) { 100 | ConnectionKey ck = node().findConnection(p); 101 | if(ck==null) 102 | continue; 103 | //Forward to peer 104 | try { 105 | forwardRequest(request,ck,new ForwardedRequestState(connkey,request.hdr.hop_by_hop_identifier)); 106 | return; 107 | } catch(StaleConnectionException ex) { 108 | //Unlucky 109 | } catch(NotARequestException ex) { 110 | //never happens 111 | } catch(NotProxiableException ex) { 112 | //Imbecile peer 113 | } 114 | } 115 | //Could not forward to any of the upstream hosts 116 | rejectRequest(request,connkey,ProtocolConstants.DIAMETER_RESULT_UNABLE_TO_DELIVER); 117 | } 118 | } 119 | 120 | protected void handleAnswer(Message answer, ConnectionKey answer_connkey, Object state) { 121 | //Since we never originates requests ourselves, and the NodeManager protects us against unsolicited/unmatched answers it is very simple to handle 122 | ForwardedRequestState frs = (ForwardedRequestState)state; 123 | try { 124 | answer.hdr.hop_by_hop_identifier=frs.hop_by_hop_identifier; 125 | forwardAnswer(answer,frs.connkey); 126 | } catch(StaleConnectionException ex) { 127 | //Can happen - there is nothing we can do about it 128 | } catch(NotAnAnswerException ex) { 129 | //never happens 130 | } catch(NotProxiableException ex) { 131 | //??? 132 | } 133 | } 134 | 135 | public static final void main(String args[]) throws Exception { 136 | if(args.length<5) { 137 | System.out.println("Usage: [upstream-host...]"); 138 | return; 139 | } 140 | 141 | int vendor_id = Integer.parseInt(args[0]); 142 | String host_id = args[1]; 143 | String realm = args[2]; 144 | int port = Integer.parseInt(args[3]); 145 | 146 | Capability capability = new Capability(); 147 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_RELAY); 148 | capability.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_RELAY); 149 | 150 | NodeSettings node_settings; 151 | try { 152 | node_settings = new NodeSettings( 153 | host_id, realm, 154 | vendor_id, 155 | capability, 156 | port, 157 | "simple_relay (JavaDiameter)", 0x01000000); 158 | } catch (InvalidSettingException e) { 159 | System.out.println(e.toString()); 160 | return; 161 | } 162 | 163 | simple_relay sr = new simple_relay(node_settings); 164 | 165 | sr.start(); 166 | 167 | //Add the upstream hosts as persistent peers 168 | for(int i=4; i 11 | * AAASession instances logs with the name "dk.i1.diameter.session.AASession", so 12 | * you can get detailed logging (including hex-dumps of incoming and outgoing 13 | * packets) by putting "dk.i1.diameter.session.AASession.level = ALL" into your 14 | * log.properties file (or equivalent) 15 | */ 16 | public class AASession extends BaseSession { 17 | static private Logger logger = Logger.getLogger("dk.i1.diameter.session.AASession"); 18 | public AASession(int auth_app_id, SessionManager session_manager) { 19 | super(auth_app_id,session_manager); 20 | } 21 | 22 | /** 23 | * Handle an answer. 24 | * This implementation handles AAA messages. All other messages are 25 | * passed to {@link BaseSession#handleAnswer} 26 | */ 27 | public void handleAnswer(Message answer, Object state) { 28 | switch(answer.hdr.command_code) { 29 | case ProtocolConstants.DIAMETER_COMMAND_AA: 30 | handleAAA(answer);; 31 | break; 32 | default: 33 | super.handleAnswer(answer,state); 34 | break; 35 | } 36 | } 37 | 38 | /** 39 | * Handle an answer. 40 | * This implementation handles missing AAA messages by calling 41 | * authFailed(). All other non-answers are passed to {@link BaseSession#handleNonAnswer} 42 | * passed to {@link BaseSession#handleAnswer} 43 | */ 44 | public void handleNonAnswer(int command_code, Object state) { 45 | switch(command_code) { 46 | case ProtocolConstants.DIAMETER_COMMAND_AA: 47 | if(authInProgress()) 48 | authFailed(null); 49 | else 50 | logger.log(Level.INFO,"Got a non-answer AA for session '"+sessionId()+"' when no reauth was progress."); 51 | break; 52 | default: 53 | super.handleNonAnswer(command_code,state); 54 | break; 55 | } 56 | } 57 | 58 | /** 59 | * Handle a AAA message. 60 | * If the result-code is success then processAAAInfo and authSuccessful are called. 61 | * If the result-code is multi-round-auth a new AAR is initiated. 62 | * If the result-code is authorization-reject then the session is closed. 63 | * If the result-code is anything else then the session is also closed. 64 | */ 65 | public void handleAAA(Message msg) { 66 | logger.log(Level.FINER,"Handling AAA"); 67 | if(!authInProgress()) 68 | return; 69 | authInProgress(false); 70 | if(state()==State.discon) 71 | return; 72 | int result_code = getResultCode(msg); 73 | switch(result_code) { 74 | case ProtocolConstants.DIAMETER_RESULT_SUCCESS: { 75 | State new_state; 76 | if(processAAAInfo(msg)) { 77 | authSuccessful(msg); 78 | } else { 79 | closeSession(msg,ProtocolConstants.DI_TERMINATION_CAUSE_DIAMETER_BAD_ANSWER); 80 | } 81 | break; 82 | } 83 | case ProtocolConstants.DIAMETER_RESULT_MULTI_ROUND_AUTH: 84 | sendAAR(); 85 | break; 86 | case ProtocolConstants.DIAMETER_RESULT_AUTHORIZATION_REJECTED: 87 | logger.log(Level.INFO,"Authorization for session "+sessionId()+" rejected, closing session"); 88 | if(state()==State.pending) 89 | closeSession(msg,ProtocolConstants.DI_TERMINATION_CAUSE_DIAMETER_BAD_ANSWER); 90 | else 91 | closeSession(msg,ProtocolConstants.DI_TERMINATION_CAUSE_DIAMETER_AUTH_EXPIRED); 92 | break; 93 | default: 94 | logger.log(Level.INFO,"AAR failed, result_code="+result_code); 95 | closeSession(msg,ProtocolConstants.DI_TERMINATION_CAUSE_DIAMETER_BAD_ANSWER); 96 | break; 97 | } 98 | } 99 | 100 | protected void startAuth() { 101 | sendAAR(); 102 | } 103 | protected void startReauth() { 104 | sendAAR(); 105 | } 106 | 107 | private final void sendAAR() { 108 | logger.log(Level.FINE,"Considering sending AAR for "+sessionId()); 109 | if(authInProgress()) 110 | return; 111 | logger.log(Level.FINE,"Sending AAR for "+sessionId()); 112 | authInProgress(true); 113 | Message aar = new Message(); 114 | aar.hdr.setRequest(true); 115 | aar.hdr.setProxiable(true); 116 | aar.hdr.application_id = authAppId(); 117 | aar.hdr.command_code = ProtocolConstants.DIAMETER_COMMAND_AA; 118 | collectAARInfo(aar); 119 | Utils.setMandatory_RFC3588(aar); 120 | try { 121 | sessionManager().sendRequest(aar,this,null); 122 | //logger.log(Level.FINER,"AAR sent"); 123 | } catch(dk.i1.diameter.node.NotARequestException ex) { 124 | //never happens 125 | } catch(dk.i1.diameter.node.NotRoutableException ex) { 126 | logger.log(Level.INFO,"Could not send AAR for session "+sessionId(),ex); 127 | authFailed(null); 128 | } 129 | } 130 | /** 131 | * Collect information to send in AAR. 132 | * This method must be overridden in subclasses to provide essential 133 | * information such as user-name, password, credenticals, etc. 134 | * This implementation calls {@link BaseSession#addCommonStuff} and adds the auth-application-id. 135 | * A subclass probably want to call this method first and then add the session-specific AVPs, eg: 136 | *
137 | 	   void collectAARInfo(Message request) { //method in your session class
138 | 	       AASession.collectAARInfo(request);
139 | 	       request.add(new AVP_UTF8String(ProtocolConstants.DI_CALLING_STATION_ID,msisdn));
140 | 	       ...
141 | 	   }
142 | 	 * 
143 | */ 144 | protected void collectAARInfo(Message request) { 145 | addCommonStuff(request); 146 | request.add(new AVP_Unsigned32(ProtocolConstants.DI_AUTH_APPLICATION_ID,authAppId())); 147 | //subclasses really need to override this 148 | } 149 | /** 150 | * Process information AAA message. 151 | * This method extracts authorization-lifetime, auth-grace-period, 152 | * session-timeout and auth-session-state AVPs and processes them. 153 | * Subclasses probably want to override this to add additional processing. 154 | */ 155 | protected boolean processAAAInfo(Message answer) { 156 | logger.log(Level.FINE,"Processing AAA info"); 157 | //subclasses probably want to override this 158 | 159 | //grab a few AVPs 160 | try { 161 | AVP avp; 162 | long auth_lifetime=0; 163 | avp = answer.find(ProtocolConstants.DI_AUTHORIZATION_LIFETIME); 164 | if(avp!=null) 165 | auth_lifetime = new AVP_Unsigned32(avp).queryValue()*1000; 166 | long auth_grace_period=0; 167 | avp = answer.find(ProtocolConstants.DI_AUTH_GRACE_PERIOD); 168 | if(avp!=null) 169 | auth_grace_period = new AVP_Unsigned32(avp).queryValue()*1000; 170 | avp = answer.find(ProtocolConstants.DI_SESSION_TIMEOUT); 171 | if(avp!=null) { 172 | int session_timeout = new AVP_Unsigned32(avp).queryValue(); 173 | updateSessionTimeout(session_timeout); 174 | } 175 | avp = answer.find(ProtocolConstants.DI_AUTH_SESSION_STATE); 176 | if(avp!=null) { 177 | int state_maintained = new AVP_Unsigned32(avp).queryValue(); 178 | stateMaintained(state_maintained==0); 179 | } 180 | 181 | long now = System.currentTimeMillis(); 182 | logger.log(Level.FINER,"Session "+sessionId()+": now="+now+" auth_lifetime="+auth_lifetime+" auth_grace_period="+auth_grace_period); 183 | session_auth_timers.updateTimers(now,auth_lifetime,auth_grace_period); 184 | logger.log(Level.FINER,"getNextReauthTime="+session_auth_timers.getNextReauthTime()+" getMaxTimeout="+session_auth_timers.getMaxTimeout()); 185 | } catch(dk.i1.diameter.InvalidAVPLengthException ex) { 186 | return false; 187 | } 188 | return true; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /dk/i1/diameter/AVP.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | 3 | /** 4 | * A Diameter AVP. 5 | * See RFC3588 section 4 for details. 6 | * An AVP consists of a code, some flags, an optional vendor ID, and a payload. 7 | *

8 | * The payload is not checked for correct size and content until you try to 9 | * construct one of its subclasses from it. Eg 10 | *

 11 |  * AVP avp = ...
 12 |  * try {
 13 |  *     int application_id = new AVP_Unsigned32(avp).queryValue());
 14 |  *     ...
 15 |  * } catch({@link InvalidAVPLengthException} ex) {
 16 |  *     ..
 17 |  * }
 18 |  * 
19 | * @see ProtocolConstants 20 | */ 21 | public class AVP { 22 | byte payload[]; 23 | 24 | /**The AVP code*/ 25 | public int code; 26 | /**The flags except the vendor flag*/ 27 | private int flags; 28 | /**The vendor ID. Assigning directly to this has the delayed effect of of setting/unsetting the vendor flag bit*/ 29 | public int vendor_id; 30 | 31 | private static final int avp_flag_vendor = 0x80; 32 | private static final int avp_flag_mandatory = 0x40; 33 | private static final int avp_flag_private = 0x20; 34 | 35 | /** Default constructor 36 | * The code and vendor_id are initialized to 0, no flags are set, and the payload is null. 37 | */ 38 | public AVP() { 39 | } 40 | /** Copy constructor (deep copy)*/ 41 | public AVP(AVP a) { 42 | payload = new byte[a.payload.length]; 43 | System.arraycopy(a.payload,0, payload,0, a.payload.length); 44 | code = a.code; 45 | flags = a.flags; 46 | vendor_id = a.vendor_id; 47 | } 48 | /** 49 | * Create AVP with code and payload 50 | * @param code 51 | * @param payload 52 | */ 53 | public AVP(int code, byte payload[]) { 54 | this(code,0,payload); 55 | } 56 | /** 57 | * Create AVP with code and payload 58 | * @param code 59 | * @param vendor_id 60 | * @param payload 61 | */ 62 | public AVP(int code, int vendor_id, byte payload[]) { 63 | this.code = code; 64 | this.vendor_id = vendor_id; 65 | this.payload = payload; 66 | } 67 | 68 | static final int decodeSize(byte [] b, int offset, int bytes) { 69 | if(bytes<8) 70 | return 0; //garbage 71 | int flags_and_length = packunpack.unpack32(b,offset+4); 72 | int flags_ = (flags_and_length>>24)&0xff; 73 | int length = flags_and_length&0x00FFFFFF; 74 | int padded_length = (length+3)&~3; 75 | if((flags_&avp_flag_vendor)!=0) { 76 | if(length<12) 77 | return 0; //garbage 78 | } else { 79 | if(length<8) 80 | return 0; //garbage 81 | } 82 | return padded_length; 83 | } 84 | 85 | boolean decode(byte [] b, int offset, int bytes) { 86 | if(bytes<8) return false; 87 | int i=0; 88 | code = packunpack.unpack32(b,offset+i); 89 | i += 4; 90 | int flags_and_length = packunpack.unpack32(b,offset+i); 91 | i += 4; 92 | flags = (flags_and_length>>24)&0xff; 93 | int length = flags_and_length&0x00FFFFFF; 94 | int padded_length = (length+3)&~3; 95 | if(bytes!=padded_length) return false; 96 | length -= 8; 97 | if((flags&avp_flag_vendor)!=0) { 98 | if(length<4) return false; 99 | vendor_id = packunpack.unpack32(b,offset+i); 100 | i += 4; 101 | length -= 4; 102 | } else 103 | vendor_id = 0; 104 | setPayload(b,offset+i,length); 105 | i += length; 106 | return true; 107 | } 108 | 109 | int encodeSize() { 110 | int sz = 4 + 4; 111 | if(vendor_id!=0) 112 | sz += 4; 113 | sz+= (payload.length+3)&~3; 114 | return sz; 115 | } 116 | 117 | int encode(byte b[], int offset) { 118 | int sz = 4 + 4; 119 | if(vendor_id!=0) 120 | sz += 4; 121 | sz += payload.length; 122 | 123 | int f=flags; 124 | if(vendor_id!=0) 125 | f |= avp_flag_vendor; 126 | else 127 | f &= ~avp_flag_vendor; 128 | 129 | int i=0; 130 | 131 | packunpack.pack32(b,offset+i, code); 132 | i += 4; 133 | packunpack.pack32(b,offset+i, sz | (f<<24)); 134 | i += 4; 135 | if(vendor_id!=0) { 136 | packunpack.pack32(b,offset+i, vendor_id); 137 | i += 4; 138 | } 139 | 140 | System.arraycopy(payload,0, b, offset+i, payload.length); 141 | 142 | return encodeSize(); 143 | } 144 | byte[] encode() { 145 | int sz = 4 + 4; 146 | if(vendor_id!=0) 147 | sz += 4; 148 | sz += payload.length; 149 | 150 | int f=flags; 151 | if(vendor_id!=0) 152 | f |= avp_flag_vendor; 153 | else 154 | f &= ~avp_flag_vendor; 155 | 156 | byte[] b = new byte[encodeSize()]; 157 | 158 | int i=0; 159 | 160 | packunpack.pack32(b,i, code); 161 | i += 4; 162 | packunpack.pack32(b,i, sz | (f<<24)); 163 | i += 4; 164 | if(vendor_id!=0) { 165 | packunpack.pack32(b,i, vendor_id); 166 | i += 4; 167 | } 168 | 169 | System.arraycopy(payload,0, b,i, payload.length); 170 | 171 | return b; 172 | } 173 | 174 | /** Returns the payload of the AVP 175 | * Returns a copy of the (unpadded) payload of the AVP in network byte 176 | * order. This is normally not what you want. The subclasses have much 177 | * nicer interfaces. 178 | * @since 0.9.6.5 179 | */ 180 | public byte[] queryPayload() { 181 | byte tmp[] = new byte[payload.length]; 182 | System.arraycopy(payload,0, tmp,0, payload.length); 183 | return tmp; 184 | } 185 | 186 | int queryPayloadSize() { return payload.length; } 187 | 188 | void setPayload(byte[] payload_) { 189 | setPayload(payload_,0,payload_.length); 190 | } 191 | 192 | void setPayload(byte[] b, int from, int count) { 193 | byte[] new_payload = new byte[count]; 194 | System.arraycopy(b,from, new_payload,0, count); 195 | payload = new_payload; 196 | } 197 | 198 | /**Returns if the AVP is vendor-specific (has non-zero vendor_id)*/ 199 | public boolean isVendorSpecific() { return vendor_id!=0; } 200 | /**Returns if the mandatory (M) flag is set*/ 201 | public boolean isMandatory() { return (flags&avp_flag_mandatory)!=0; } 202 | /**Returns if the private (P) flag is set*/ 203 | public boolean isPrivate() { return (flags&avp_flag_private)!=0; } 204 | /**Sets/unsets the mandatory (M) flag*/ 205 | public void setMandatory(boolean b) { 206 | if(b) flags |= avp_flag_mandatory; 207 | else flags &= ~avp_flag_mandatory; 208 | } 209 | /**Sets/unsets the private (P) flag*/ 210 | public void setPrivate(boolean b) { 211 | if(b) flags |= avp_flag_private; 212 | else flags &= ~avp_flag_private; 213 | } 214 | /**Sets the M-bit and returns the instance. 215 | *The M-bit (mandatory) is set and the instance is returned. 216 | *While this is very very simple, it can be useful when constructing 217 | *grouped AVPs and you need to set the M-bit on embedded AVPs, as in the following example: 218 |
219 | 	  ccr.add(new AVP_Grouped(ProtocolConstants.DI_REQUESTED_SERVICE_UNIT,
220 | 	                          new AVP[] {new AVP_Unsigned64(ProtocolConstants.DI_CC_SERVICE_SPECIFIC_UNITS,42).setM()}
221 | 	                         )
222 | 	         );
223 | 	  
224 | where the alternative would have been more cumbersome: 225 |
226 | 	  AVP tmp-avp = new AVP_Unsigned64(ProtocolConstants.DI_CC_SERVICE_SPECIFIC_UNITS,42);
227 | 	  tmp_avp.setMandatory(true);
228 | 	  ccr.add(new AVP_Grouped(ProtocolConstants.DI_REQUESTED_SERVICE_UNIT,
229 | 	                          new AVP[] {tmp_avp}
230 | 	                         )
231 | 	         );
232 | 	  
233 | *@since 0.9.2 234 | */ 235 | public AVP setM() { 236 | flags |= avp_flag_mandatory; 237 | return this; 238 | } 239 | 240 | /**Replace the content (and codes etc.) with the specified AVP. 241 | *@since 0.9.5 242 | */ 243 | void inline_shallow_replace(AVP a) { 244 | payload = a.payload; 245 | code = a.code; 246 | flags = a.flags; 247 | vendor_id = a.vendor_id; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /examples/cc/cc_test_server.java: -------------------------------------------------------------------------------- 1 | import dk.i1.diameter.*; 2 | import dk.i1.diameter.node.*; 3 | 4 | /** 5 | * A simple Credit-control server that accepts and grants everything 6 | */ 7 | class cc_test_server extends NodeManager { 8 | cc_test_server(NodeSettings node_settings) { 9 | super(node_settings); 10 | } 11 | 12 | public static final void main(String args[]) throws Exception { 13 | if(args.length<2) { 14 | System.out.println("Usage: []"); 15 | return; 16 | } 17 | 18 | String host_id = args[0]; 19 | String realm = args[1]; 20 | int port; 21 | if(args.length>=3) 22 | port = Integer.parseInt(args[2]); 23 | else 24 | port = 3868; 25 | 26 | Capability capability = new Capability(); 27 | capability.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_CREDIT_CONTROL); 28 | 29 | NodeSettings node_settings; 30 | try { 31 | node_settings = new NodeSettings( 32 | host_id, realm, 33 | 99999, //vendor-id 34 | capability, 35 | port, 36 | "cc_test_server", 0x01000000); 37 | } catch (InvalidSettingException e) { 38 | System.out.println(e.toString()); 39 | return; 40 | } 41 | 42 | cc_test_server tss = new cc_test_server(node_settings); 43 | tss.start(); 44 | 45 | System.out.println("Hit enter to terminate server"); 46 | System.in.read(); 47 | 48 | tss.stop(50); //Stop but allow 50ms graceful shutdown 49 | } 50 | 51 | protected void handleRequest(dk.i1.diameter.Message request, ConnectionKey connkey, Peer peer) { 52 | //this is not the way to do it, but fine for a lean-and-mean test server 53 | Message answer = new Message(); 54 | answer.prepareResponse(request); 55 | AVP avp; 56 | avp = request.find(ProtocolConstants.DI_SESSION_ID); 57 | if(avp!=null) 58 | answer.add(avp); 59 | node().addOurHostAndRealm(answer); 60 | avp = request.find(ProtocolConstants.DI_CC_REQUEST_TYPE); 61 | if(avp==null) { 62 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_MISSING_AVP, 63 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{new AVP(ProtocolConstants.DI_CC_REQUEST_TYPE,new byte[]{})})}); 64 | return; 65 | } 66 | int cc_request_type=-1; 67 | try { cc_request_type = new AVP_Unsigned32(avp).queryValue(); } catch(InvalidAVPLengthException ex) {} 68 | if(cc_request_type!=ProtocolConstants.DI_CC_REQUEST_TYPE_INITIAL_REQUEST && 69 | cc_request_type!=ProtocolConstants.DI_CC_REQUEST_TYPE_UPDATE_REQUEST && 70 | cc_request_type!=ProtocolConstants.DI_CC_REQUEST_TYPE_TERMINATION_REQUEST && 71 | cc_request_type!=ProtocolConstants.DI_CC_REQUEST_TYPE_EVENT_REQUEST) 72 | { 73 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_INVALID_AVP_VALUE, 74 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{avp})}); 75 | return; 76 | } 77 | 78 | //This test server does not support multiple-services-cc 79 | avp = request.find(ProtocolConstants.DI_MULTIPLE_SERVICES_CREDIT_CONTROL); 80 | if(avp!=null) { 81 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_INVALID_AVP_VALUE, 82 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{avp})}); 83 | return; 84 | } 85 | avp = request.find(ProtocolConstants.DI_MULTIPLE_SERVICES_INDICATOR); 86 | if(avp!=null) { 87 | int indicator=-1; 88 | try { indicator=new AVP_Unsigned32(avp).queryValue(); } catch(InvalidAVPLengthException ex) {} 89 | if(indicator!=ProtocolConstants.DI_MULTIPLE_SERVICES_INDICATOR_MULTIPLE_SERVICES_NOT_SUPPORTED) { 90 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_INVALID_AVP_VALUE, 91 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{avp})}); 92 | return; 93 | } 94 | } 95 | 96 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE,ProtocolConstants.DIAMETER_RESULT_SUCCESS)); 97 | avp = request.find(ProtocolConstants.DI_AUTH_APPLICATION_ID); 98 | if(avp!=null) 99 | answer.add(avp); 100 | avp = request.find(ProtocolConstants.DI_CC_REQUEST_TYPE); 101 | if(avp!=null) 102 | answer.add(avp); 103 | avp = request.find(ProtocolConstants.DI_CC_REQUEST_NUMBER); 104 | if(avp!=null) 105 | answer.add(avp); 106 | 107 | switch(cc_request_type) { 108 | case ProtocolConstants.DI_CC_REQUEST_TYPE_INITIAL_REQUEST: 109 | case ProtocolConstants.DI_CC_REQUEST_TYPE_UPDATE_REQUEST: 110 | case ProtocolConstants.DI_CC_REQUEST_TYPE_TERMINATION_REQUEST: 111 | //grant whatever is requested 112 | avp = request.find(ProtocolConstants.DI_REQUESTED_SERVICE_UNIT); 113 | if(avp!=null) { 114 | AVP g = new AVP(avp); 115 | g.code = ProtocolConstants.DI_GRANTED_SERVICE_UNIT; 116 | answer.add(avp); 117 | } 118 | break; 119 | case ProtocolConstants.DI_CC_REQUEST_TYPE_EVENT_REQUEST: { 120 | //examine requested-action 121 | avp = request.find(ProtocolConstants.DI_REQUESTED_ACTION); 122 | if(avp==null) { 123 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_MISSING_AVP, 124 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{new AVP(ProtocolConstants.DI_REQUESTED_ACTION,new byte[]{})})}); 125 | return; 126 | } 127 | int requested_action=-1; 128 | try { requested_action = new AVP_Unsigned32(avp).queryValue(); } catch(InvalidAVPLengthException ex) {} 129 | switch(requested_action) { 130 | case ProtocolConstants.DI_REQUESTED_ACTION_DIRECT_DEBITING: 131 | //nothing. just indicate success 132 | break; 133 | case ProtocolConstants.DI_REQUESTED_ACTION_REFUND_ACCOUNT: 134 | //nothing. just indicate success 135 | break; 136 | case ProtocolConstants.DI_REQUESTED_ACTION_CHECK_BALANCE: 137 | //report back that the user has sufficient balance 138 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_CHECK_BALANCE_RESULT, ProtocolConstants.DI_DI_CHECK_BALANCE_RESULT_ENOUGH_CREDIT)); 139 | break; 140 | case ProtocolConstants.DI_REQUESTED_ACTION_PRICE_ENQUIRY: 141 | //report back a price of DKK42.17 per kanelsnegl 142 | answer.add(new AVP_Grouped(ProtocolConstants.DI_COST_INFORMATION, 143 | new AVP[] { new AVP_Grouped(ProtocolConstants.DI_UNIT_VALUE, 144 | new AVP[] { new AVP_Integer64(ProtocolConstants.DI_VALUE_DIGITS,4217), 145 | new AVP_Integer32(ProtocolConstants.DI_EXPONENT,-2) 146 | } 147 | ), 148 | new AVP_Unsigned32(ProtocolConstants.DI_CURRENCY_CODE, 208), 149 | new AVP_UTF8String(ProtocolConstants.DI_COST_UNIT, "kanelsnegl") 150 | } 151 | ) 152 | ); 153 | break; 154 | default: { 155 | answerError(answer,connkey,ProtocolConstants.DIAMETER_RESULT_INVALID_AVP_VALUE, 156 | new AVP[] {new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{avp})}); 157 | return; 158 | } 159 | } 160 | } 161 | } 162 | 163 | Utils.setMandatory_RFC3588(answer); 164 | 165 | try { 166 | answer(answer,connkey); 167 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) { } 168 | } 169 | 170 | void answerError(dk.i1.diameter.Message answer, ConnectionKey connkey, int result_code, AVP [] error_avp) { 171 | answer.hdr.setError(true); 172 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE,result_code)); 173 | for(AVP avp:error_avp) 174 | answer.add(avp); 175 | try { 176 | answer(answer,connkey); 177 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) { } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /abnf/ABNFConverter.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.util.regex.*; 3 | import java.util.*; 4 | 5 | /** 6 | ABNFConverter - convert RFC-style ABNF to java class. 7 | This program takes as input (input redirect or file argument) something like this: 8 |
  9 |         ::= < Diameter Header: 257, REQ >
 10 |                 { Origin-Host }
 11 |                 { Origin-Realm }
 12 |              1* { Host-IP-Address }
 13 |                 { Vendor-Id }
 14 |                 { Product-Name }
 15 |                 [ Origin-State-Id ]
 16 |               * [ Supported-Vendor-Id ]
 17 |               * [ Auth-Application-Id ]
 18 |               * [ Inband-Security-Id ]
 19 |               * [ Acct-Application-Id ]
 20 |               * [ Vendor-Specific-Application-Id ]
 21 |                 [ Firmware-Revision ]
 22 |               * [ AVP ]
 23 | 
24 | This program then parses the ABNF and outputs a class that can decode/encode 25 | messages according to the ABNF. Something like this is generated: 26 |
 27 | import dk.i1.diameter.*;
 28 | import java.util.*;
 29 | 
 30 | public class CER {
 31 |         public AVP            origin_host;
 32 |         public AVP            origin_realm;
 33 |         public ArrayList host_ip_address;
 34 |         public AVP            vendor_id;
 35 | ...
 36 |         public static CER fromMessage(Message msg) {
 37 | ...
 38 |         public Message toMessage() {
 39 | ...
 40 | 
41 | The generated class also contains 42 | Utils.ABNFComponent array that should be used to validate ingoing and 43 | outgoing packets. 44 |

45 | Todo: generate native types instead of generic 'AVP' (requires dictionary); 46 | should check ABNF; should make it easy to set origin-host/realm; etc. 47 | */ 48 | public class ABNFConverter { 49 | private static class AVP { 50 | public boolean fixed_position; 51 | public boolean multiple; 52 | public int min_occurrences; 53 | public int max_occurrences; 54 | public String name; 55 | public String java_name; 56 | public String constant_name; 57 | }; 58 | 59 | public static final void main(String args[]) throws Exception { 60 | InputStream is; 61 | if(args.length==0) 62 | is = System.in; 63 | else { 64 | is = new FileInputStream(args[0]); 65 | } 66 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 67 | 68 | String message_name=null; 69 | 70 | for(;;) { 71 | String s = br.readLine(); 72 | if(s.indexOf("::=")==-1) 73 | continue; 74 | //parse line like: 75 | // ::= < Diameter Header: 257, REQ > 76 | Matcher m = Pattern.compile("([^<]*<)([^>]+).*").matcher(s); 77 | m.matches(); 78 | 79 | message_name = m.group(2); 80 | break; 81 | } 82 | 83 | System.out.println("import dk.i1.diameter.*;"); 84 | System.out.println("import java.util.*;"); 85 | System.out.println(""); 86 | System.out.println("public class "+message_name+" {"); 87 | boolean arbitrary_avps_allowed=false; 88 | 89 | ArrayList avps = new ArrayList(); 90 | for(;;) { 91 | String s = br.readLine(); 92 | if(s==null) break; 93 | 94 | Matcher m = Pattern.compile("( *)([0-9]*)(\\*)?([0-9]*) *([{\\[<]) *([a-zA-Z-]+) *([}\\]>])").matcher(s); 95 | if(m.matches()) { 96 | AVP avp = new AVP(); 97 | avp.fixed_position = m.group(5).equals("<"); 98 | avp.multiple = m.group(3)!=null; 99 | if(avp.multiple) { 100 | if(m.group(2).equals("")) 101 | avp.min_occurrences = 0; 102 | else 103 | avp.min_occurrences = Integer.valueOf(m.group(2)); 104 | if(m.group(4).equals("")) 105 | avp.max_occurrences = -1; 106 | else 107 | avp.max_occurrences = Integer.valueOf(m.group(4)); 108 | } else { 109 | if(m.group(5).equals("<") || m.group(5).equals("{")) { 110 | avp.min_occurrences = 1; 111 | avp.max_occurrences = 1; 112 | } else { 113 | avp.min_occurrences = 0; 114 | avp.max_occurrences = 1; 115 | } 116 | } 117 | avp.name = m.group(6); 118 | 119 | if(!avp.name.equals("AVP")) { 120 | avp.java_name = avp.name.toLowerCase().replace('-','_'); 121 | avp.constant_name = "DI_"+avp.name.toUpperCase().replace('-','_'); 122 | } else 123 | arbitrary_avps_allowed = true; 124 | avps.add(avp); 125 | } 126 | } 127 | 128 | for(AVP avp:avps) { 129 | String l="\t"; 130 | l+="public "; 131 | if(avp.multiple) 132 | l+="ArrayList "; 133 | else 134 | l+="AVP "; 135 | if(avp.java_name!=null) 136 | l+=avp.java_name; 137 | else 138 | l+="avp"; 139 | l+=";"; 140 | System.out.println(l); 141 | } 142 | 143 | System.out.println("\t"); 144 | 145 | //Generate constructor 146 | System.out.println("\tprivate "+message_name+"() {"); 147 | System.out.println("\t}"); 148 | 149 | System.out.println("\t"); 150 | 151 | //Generate ABNF records 152 | System.out.println("\tpublic static final Utils.ABNFComponent abnf_"+message_name.toLowerCase()+"[] = {"); 153 | for(AVP avp:avps) { 154 | String l="\t\t"; 155 | l+="new Utils.ABNFComponent("; 156 | if(avp.fixed_position) 157 | l+="true, "; 158 | else 159 | l+="false, "; 160 | l+=Integer.toString(avp.min_occurrences)+", "; 161 | l+=Integer.toString(avp.max_occurrences)+", "; 162 | if(avp.constant_name!=null) 163 | l+="ProtocolConstants."+avp.constant_name; 164 | else 165 | l+="-1"; 166 | l+="),"; 167 | System.out.println(l); 168 | 169 | } 170 | System.out.println("\t};"); 171 | 172 | System.out.println("\t"); 173 | 174 | //Generate fromMessage() 175 | System.out.println("\tpublic static "+message_name+" fromMessage(Message msg) {"); 176 | System.out.println("\t\t"+message_name+" result = new "+message_name+";"); 177 | if(arbitrary_avps_allowed) 178 | System.out.println("\t\tSet processed_avps = new HashSet();"); 179 | for(AVP avp:avps) { 180 | if(avp.java_name==null) continue; 181 | if(avp.multiple) { 182 | System.out.println("\t\tresult."+avp.java_name+" = new ArrayList();"); 183 | System.out.println("\t\tfor(AVP avp : msg.subset(ProtocolConstants."+avp.constant_name+")) {"); 184 | System.out.println("\t\t\tresult."+avp.java_name+".add(new AVP(avp));"); 185 | if(arbitrary_avps_allowed) 186 | System.out.println("\t\t\tprocessed_avps.add(avp);"); 187 | System.out.println("\t\t}"); 188 | } else { 189 | System.out.println("\t\tresult."+avp.java_name+" = msg.find("+avp.constant_name+");"); 190 | } 191 | } 192 | if(arbitrary_avps_allowed) { 193 | System.out.println("\t\tresult.avps = new ArrayList();"); 194 | System.out.println("\t\tfor(AVP avp : msg.avps()) {"); 195 | System.out.println("\t\t\tif(!processed_avps.contains(avp))"); 196 | System.out.println("\t\t\t\t result.avps.add(avp);"); 197 | System.out.println("\t\t}"); 198 | } 199 | System.out.println("\t\t"); 200 | System.out.println("\t\treturn result;"); 201 | System.out.println("\t}"); 202 | 203 | //Generate toMessage() 204 | System.out.println("\tpublic Message toMessage() {"); 205 | System.out.println("\t\tMessage msg = new Message();"); 206 | for(AVP avp:avps) { 207 | if(avp.multiple) { 208 | if(avp.java_name==null) 209 | System.out.println("\t\tfor(AVP avp : avps) {"); 210 | else 211 | System.out.println("\t\tfor(AVP avp : "+avp.java_name+") {"); 212 | System.out.println("\t\t\tmsg.add(avp);"); 213 | System.out.println("\t\t}"); 214 | } else { 215 | if(avp.min_occurrences==1) 216 | System.out.println("\t\tmsg.add("+avp.java_name+");"); 217 | else { 218 | System.out.println("\t\tif("+avp.java_name+"!=null)"); 219 | System.out.println("\t\t\tmsg.add("+avp.java_name+");"); 220 | } 221 | } 222 | } 223 | System.out.println("\t\t"); 224 | System.out.println("\t\treturn msg;"); 225 | System.out.println("\t}"); 226 | 227 | System.out.println("}"); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/Capability.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.util.*; 3 | import dk.i1.diameter.ProtocolConstants; 4 | 5 | /** 6 | * A bag of supported/allowed applications. 7 | * A Capability instance is used by the {@link Node} class to announce this node's 8 | * capabilities in terms of supported/allowed authentication/authorization/accounting 9 | * applications, vendor-specific applications and vendor-IDs. It is also used by the 10 | * Node to catch messages that belong to applications that have not been announced or 11 | * is being sent to peers that do not support it. 12 | *

13 | * An application is set of message commands with defined semantics. Examples are 14 | * NASREQ (mostly dial-up sessions), MOBILEIP (mobile IPv4/IPv6 and asociated 15 | * roaming), EAP (extensible authentication). Each aplication can be used for 16 | * authentication/authorization, accounting, or both. 17 | * Each diameter message identifies which application it belongs to. Messages 18 | * belonging to applications that have not been negotiated are rejected. 19 | * Special rules applies to the "common" application (low-level diameter control 20 | * message) and peers that announce the "relay" application. 21 | *

The announced vendor-IDs can be used for implementing tweaks in areas not 22 | * fully specified in the application specification. 23 | *

24 | * {@link dk.i1.diameter.ProtocolConstants} contains definitions for some of the standard applications (DIAMETER_APPLICATION_....) 25 | *

26 | * A hypothetical node that supports NASREQ and EAP could create the capability 27 | * set like this: 28 | *

 29 | Capability cap = new Capability();
 30 | cap.addSupportedVendor(a vendor-id);
 31 | cap.addSupportedVendor(another vendor-id);
 32 | cap.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ);
 33 | cap.addAcctApp(ProtocolConstants.DIAMETER_APPLICATION_NASREQ);
 34 | cap.addAuthApp(ProtocolConstants.DIAMETER_APPLICATION_EAP);
 35 |  * 
36 | * A hypothetical node that only supports a vendor-specific accounting 37 | * extension could create the capability set like this: 38 | *
 39 | static final int our_vendor_id = ...;
 40 | static final int our_application_id = ...;
 41 | Capability cap = new Capability();
 42 | cap.addSupportedVendor(our_vendor_id);
 43 | cap.addVendorAcctApp(our_vendor_id,our_application_id);
 44 | 
45 | */ 46 | public class Capability { 47 | static class VendorApplication { 48 | public int vendor_id; 49 | public int application_id; 50 | public VendorApplication(int vendor_id, int application_id) { 51 | this.vendor_id = vendor_id; 52 | this.application_id = application_id; 53 | } 54 | public int hashCode() { 55 | return vendor_id + application_id; 56 | } 57 | public boolean equals(Object obj) { 58 | if(this==obj) 59 | return true; 60 | if(obj==null || obj.getClass()!=this.getClass()) 61 | return false; 62 | return ((VendorApplication)obj).vendor_id == vendor_id && 63 | ((VendorApplication)obj).application_id == application_id; 64 | } 65 | } 66 | 67 | Set supported_vendor; 68 | Set auth_app; 69 | Set acct_app; 70 | Set auth_vendor; 71 | Set acct_vendor; 72 | 73 | /**Constructor. 74 | * The instance is initialized to be empty. 75 | */ 76 | public Capability() { 77 | supported_vendor = new HashSet(); 78 | auth_app = new HashSet(); 79 | acct_app = new HashSet(); 80 | auth_vendor = new HashSet(); 81 | acct_vendor = new HashSet(); 82 | } 83 | /**Copy-Constructor (deep copy). 84 | */ 85 | public Capability(Capability c) { 86 | supported_vendor = new HashSet(); 87 | for(Integer i:c.supported_vendor) 88 | supported_vendor.add(i); 89 | auth_app = new HashSet(); 90 | for(Integer i:c.auth_app) 91 | auth_app.add(i); 92 | acct_app = new HashSet(); 93 | for(Integer i:c.acct_app) 94 | acct_app.add(i); 95 | auth_vendor = new HashSet(); 96 | for(VendorApplication va:c.auth_vendor) 97 | auth_vendor.add(va); 98 | acct_vendor = new HashSet(); 99 | for(VendorApplication va:c.acct_vendor) 100 | acct_vendor.add(va); 101 | } 102 | 103 | /**Returns if the specified vendor ID is supported*/ 104 | public boolean isSupportedVendor(int vendor_id) { 105 | return supported_vendor.contains(vendor_id); 106 | } 107 | /** 108 | * Returns if the specified application is an allowed auth-application. 109 | * If the application "relay" is listen, then the auth-application is always allowed. 110 | */ 111 | public boolean isAllowedAuthApp(int app) { 112 | return auth_app.contains(app) || 113 | auth_app.contains(ProtocolConstants.DIAMETER_APPLICATION_RELAY); 114 | } 115 | /** 116 | * Returns if the specified application is an allowed auth-application. 117 | * If the application "relay" is listen, then the auth-application is always allowed. 118 | */ 119 | public boolean isAllowedAcctApp(int app) { 120 | return acct_app.contains(app) || 121 | acct_app.contains(ProtocolConstants.DIAMETER_APPLICATION_RELAY); 122 | } 123 | /** 124 | * Returns if the specified vendor-specific application is an allowed auth-application. 125 | */ 126 | public boolean isAllowedAuthApp(int vendor_id, int app) { 127 | return auth_vendor.contains(new VendorApplication(vendor_id,app)); 128 | } 129 | /** 130 | * Returns if the specified vendor-specific application is an allowed auth-application. 131 | */ 132 | public boolean isAllowedAcctApp(int vendor_id, int app) { 133 | return acct_vendor.contains(new VendorApplication(vendor_id,app)); 134 | } 135 | 136 | public void addSupportedVendor(int vendor_id) { 137 | supported_vendor.add(vendor_id); 138 | } 139 | public void addAuthApp(int app) { 140 | auth_app.add(app); 141 | } 142 | public void addAcctApp(int app) { 143 | acct_app.add(app); 144 | } 145 | public void addVendorAuthApp(int vendor_id, int app) { 146 | auth_vendor.add(new VendorApplication(vendor_id,app)); 147 | } 148 | public void addVendorAcctApp(int vendor_id, int app) { 149 | acct_vendor.add(new VendorApplication(vendor_id,app)); 150 | } 151 | 152 | /** 153 | * Returns if no applications are allowed/supported 154 | */ 155 | public boolean isEmpty() { 156 | return auth_app.isEmpty() && 157 | acct_app.isEmpty() && 158 | auth_vendor.isEmpty() && 159 | acct_vendor.isEmpty(); 160 | } 161 | 162 | /** 163 | * Create a capability intersection. 164 | */ 165 | public static Capability calculateIntersection(Capability us, Capability peer) { 166 | //assumption: we are not a relay 167 | Capability c = new Capability(); 168 | for(Integer vendor_id : peer.supported_vendor) { 169 | if(us.isSupportedVendor(vendor_id)) 170 | c.addSupportedVendor(vendor_id); 171 | } 172 | 173 | for(Integer app : peer.auth_app) { 174 | if(app==ProtocolConstants.DIAMETER_APPLICATION_RELAY || 175 | us.auth_app.contains(app) || 176 | us.auth_app.contains(ProtocolConstants.DIAMETER_APPLICATION_RELAY)) 177 | c.addAuthApp(app); 178 | } 179 | for(Integer app : peer.acct_app) { 180 | if(app==ProtocolConstants.DIAMETER_APPLICATION_RELAY || 181 | us.acct_app.contains(app) || 182 | us.acct_app.contains(ProtocolConstants.DIAMETER_APPLICATION_RELAY)) 183 | c.addAcctApp(app); 184 | } 185 | for(VendorApplication va : peer.auth_vendor) { 186 | //relay app is not well-defined for vendor-app 187 | if(us.isAllowedAuthApp(va.vendor_id,va.application_id)) 188 | c.addVendorAuthApp(va.vendor_id,va.application_id); 189 | } 190 | for(VendorApplication va : peer.acct_vendor) { 191 | //relay app is not well-defined for vendor-app 192 | if(us.isAllowedAcctApp(va.vendor_id,va.application_id)) 193 | c.addVendorAcctApp(va.vendor_id,va.application_id); 194 | } 195 | return c; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/Peer.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.net.*; 3 | import java.util.StringTokenizer; 4 | 5 | /** 6 | * A Diameter peer. 7 | * Hmmmm. more documentation here... 8 | *

9 | * Note: According to DIME WG (Diameter Maintanence and Extentions Working Group) 10 | * the use of raw ip-address as host identities is non-compliant, so it is 11 | * strongly encouraged to use fully-qualified domain names 12 | */ 13 | public class Peer { 14 | private String host; 15 | private int port; 16 | private boolean secure; 17 | public enum TransportProtocol { 18 | tcp, 19 | sctp 20 | }; 21 | TransportProtocol transport_protocol; 22 | 23 | /** 24 | * Constructs a peer from an IP address. 25 | * The address is set to the specified address, the port is set to 3868, 26 | * and the secure setting is off. 27 | * @param address The IP address of the peer. 28 | * @deprecated Use hostname instead of raw IP-address (preferably a FQDN) 29 | */ 30 | public Peer(InetAddress address) { 31 | this(address,TransportProtocol.tcp); 32 | } 33 | /** 34 | * Constructs a peer from an IP address and transport-protocol. 35 | * The address is set to the specified address, the port is set to 3868, 36 | * and the secure setting is off. 37 | * @param address The IP address of the peer. 38 | * @param transport_protocol TCP or SCTP 39 | * @since 0.9.5 40 | * @deprecated Use hostname instead of raw IP-address (preferably a FQDN) 41 | */ 42 | public Peer(InetAddress address, TransportProtocol transport_protocol) { 43 | this(address,3868,transport_protocol); 44 | } 45 | /** 46 | * Constructs a peer from an IP address. 47 | * The address is set to the specified address, the port is set to the 48 | * specified port, and the secure setting is off. 49 | * @param address The IP address of the peer. 50 | * @param port The port of the peer. 51 | * @deprecated Use hostname instead of raw IP-address (preferably a FQDN) 52 | */ 53 | public Peer(InetAddress address, int port) { 54 | this(address,port,TransportProtocol.tcp); 55 | } 56 | /** 57 | * Constructs a peer from IP address + port + transport-protocol. 58 | * The address is set to the specified address, the port is set to the 59 | * specified port, and the secure setting is off. 60 | * @param address The IP address of the peer. 61 | * @param port The port of the peer. 62 | * @param transport_protocol TCP or SCTP 63 | * @since 0.9.5 64 | * @deprecated Use hostname instead of raw IP-address (preferably a FQDN) 65 | */ 66 | public Peer(InetAddress address, int port, TransportProtocol transport_protocol) { 67 | this.host = address.getHostAddress(); 68 | this.port = port; 69 | this.secure = false; 70 | this.transport_protocol = transport_protocol; 71 | } 72 | /** 73 | * Constructs a peer from a host name. 74 | * The address is set to the specified host-name. The IP-address of the 75 | * host is not immediately resolved. The port is set to 3868, 76 | * and the secure setting is off. 77 | * @param host The host-name of the peer (preferably fully-qualified) 78 | */ 79 | public Peer(String host) throws EmptyHostNameException { 80 | this(host,3868); 81 | } 82 | /** 83 | * Constructs a peer from a host name and port. 84 | * The address is set to the specified host-name. The IP-address of the 85 | * host is not immediately resolved. The port is set to the specified 86 | * port, and the secure setting is off. 87 | * @param host The host-name of the peer (preferably fully-qualified) 88 | * @param port The port of the peer. 89 | */ 90 | public Peer(String host, int port) throws EmptyHostNameException { 91 | this(host,port,TransportProtocol.tcp); 92 | } 93 | /** 94 | * Constructs a peer from a host name, port and transport-protocol. 95 | * The address is set to the specified host-name. The IP-address of the 96 | * host is not immediately resolved. The port is set to the specified 97 | * port, and the secure setting is off. 98 | * @param host The host-name of the peer (preferably fully-qualified) 99 | * @param port The port of the peer. 100 | * @param transport_protocol TCP or SCTP 101 | * @since 0.9.5 102 | */ 103 | public Peer(String host, int port, TransportProtocol transport_protocol) throws EmptyHostNameException { 104 | if(host.length()==0) 105 | throw new EmptyHostNameException(); 106 | this.host = host; 107 | this.port = port; 108 | this.secure = false; 109 | this.transport_protocol = transport_protocol; 110 | } 111 | /** 112 | * Constructs a peer from a socket address. 113 | * The address and port is set to the specifed socket address. 114 | * The secure setting is off. 115 | * @param address The socket address of the peer. 116 | * @deprecated Use hostname instead of raw IP-address (preferably a FQDN) 117 | */ 118 | public Peer(InetSocketAddress address) { 119 | this.host = address.getAddress().getHostAddress(); 120 | this.port = address.getPort(); 121 | this.secure = false; 122 | this.transport_protocol = TransportProtocol.tcp; 123 | } 124 | /** 125 | * Constructs a peer from a URI. 126 | * Only URIs as specified in 127 | * RFC3855 section 4.3 are supported. Please note that the [transport] 128 | * and [protocol] part are not supported and ignored because the URI 129 | * class has problems with parsing them. 130 | * @param uri The Diameter URI 131 | */ 132 | public Peer(URI uri) throws UnsupportedURIException { 133 | if(uri.getScheme()!=null && !uri.getScheme().equals("aaa") && !uri.getScheme().equals("aaas")) 134 | throw new UnsupportedURIException("Only aaa: schemes are supported"); 135 | if(uri.getUserInfo()!=null) 136 | throw new UnsupportedURIException("userinfo not supported in Diameter URIs"); 137 | if(uri.getPath()!=null && !uri.getPath().equals("")) 138 | throw new UnsupportedURIException("path not supported in Diameter URIs"); 139 | host = uri.getHost(); 140 | port = uri.getPort(); 141 | if(port==-1) port=3868; 142 | secure = uri.getScheme().equals("aaas"); 143 | transport_protocol = TransportProtocol.tcp; 144 | } 145 | /** Copy constructor (deep copy)*/ 146 | public Peer(Peer p) { 147 | this.host = p.host; 148 | this.port = p.port; 149 | this.secure = p.secure; 150 | if(p.capabilities!=null) 151 | this.capabilities = new Capability(p.capabilities); 152 | transport_protocol = p.transport_protocol; 153 | 154 | } 155 | /**Capabilities of this peer*/ 156 | public Capability capabilities; 157 | 158 | /** 159 | * Returns the Diameter URI of the peer 160 | * @return the Diameter URI of the peer, eg. "aaa://somehost.example.net" 161 | */ 162 | public URI uri() { 163 | try { 164 | return new URI(secure?"aaas":"aaa", null, host, port, null,null,null); 165 | } catch(java.net.URISyntaxException e) {} 166 | return null; 167 | } 168 | 169 | /** 170 | * Creates a peer from a Diameter URI string. 171 | * @param s The Diameter URI string, eg. "aaa://somehost.example.net" 172 | */ 173 | public static Peer fromURIString(String s) throws UnsupportedURIException { 174 | //The URI class has problems with DiameterURIs with transport or protocol parts 175 | //We have to parse them ourselves 176 | int i = s.indexOf(';'); 177 | String extra_stuff = null; 178 | if(i!=-1) { 179 | extra_stuff = s.substring(i+1); 180 | s = s.substring(0,i); 181 | } 182 | URI uri; 183 | try { 184 | uri = new URI(s); 185 | } catch(java.net.URISyntaxException e) { 186 | throw new UnsupportedURIException(e); 187 | } 188 | Peer p = new Peer(uri); 189 | if(extra_stuff!=null) { 190 | StringTokenizer st1 = new StringTokenizer(extra_stuff,";"); 191 | while(st1.hasMoreTokens()) { 192 | String part = st1.nextToken(); 193 | StringTokenizer st2 = new StringTokenizer(part,"="); 194 | if(!st2.hasMoreTokens()) 195 | continue; 196 | String element_name = st2.nextToken(); 197 | if(!element_name.equals("transport")) 198 | continue; 199 | if(!st2.hasMoreTokens()) 200 | continue; 201 | String element_value = st2.nextToken(); 202 | if(element_value.equals("sctp")) 203 | p.transport_protocol = TransportProtocol.sctp; 204 | else if(element_value.equals("tcp")) 205 | p.transport_protocol = TransportProtocol.tcp; 206 | else 207 | throw new UnsupportedURIException("Unknown transport-protocol: "+ element_value); 208 | } 209 | } 210 | return p; 211 | } 212 | 213 | public String host() { 214 | return host; 215 | } 216 | public void host(String host) { 217 | this.host = host; 218 | } 219 | public int port() { 220 | return port; 221 | } 222 | public void port(int port) { 223 | this.port = port; 224 | } 225 | public boolean secure() { 226 | return secure; 227 | } 228 | public void secure(boolean secure) { 229 | this.secure = secure; 230 | } 231 | /** 232 | * @since 0.9.5 233 | */ 234 | public TransportProtocol transportProtocol() { 235 | return transport_protocol; 236 | } 237 | /** 238 | * @since 0.9.5 239 | */ 240 | public void transportProtocol(TransportProtocol transport_protocol) { 241 | this.transport_protocol = transport_protocol; 242 | } 243 | 244 | public String toString() { 245 | return (secure?"aaas":"aaa") 246 | + "://" 247 | + host 248 | + ":" 249 | + (Integer.valueOf(port)).toString() 250 | + (transport_protocol==TransportProtocol.tcp?"":";transport=sctp") 251 | ; 252 | } 253 | 254 | public int hashCode() { 255 | return port + host.hashCode(); 256 | } 257 | 258 | public boolean equals(Object o) { 259 | if(this==o) 260 | return true; 261 | if(o==null || o.getClass()!=this.getClass()) 262 | return false; 263 | Peer p=(Peer)o; 264 | return port==p.port && 265 | host.equals(p.host); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/NodeSettings.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.util.Random; 3 | 4 | /** 5 | * Configuration for a node. 6 | * NodeSettings contain the settings for a node including node ID, capabilities, etc. 7 | * A NodeSettings instance is required for initializing {@link Node} and {@link NodeManager} 8 | *

Example for constructing a NodeSettings instance: 9 | *

 10 |  *  Capability capability = new Capability();
 11 |  *  capability.addAuthApp(...);
 12 |  *  capability.addAcctApp(...);
 13 |  *  
 14 |  *  NodeSettings node_settings;
 15 |  *  try {
 16 |  *      node_settings  = new NodeSettings(
 17 |  *          "somehost.example.net", "example.net",
 18 |  *          0, //vendor-id. 0 is not a valid value.
 19 |  *          capability,
 20 |  *          3868,
 21 |  *          "ExampleNet gateway", 0x01000000);
 22 |  *  } catch (InvalidSettingException ex) {
 23 |  *      System.out.println(ex.toString());
 24 |  *      return;
 25 |  *  }
 26 |  *
27 | * Does and donts: 28 | *
    29 | *
  • The Diameter host ID will normally be the same as the fully-qualified 30 | * domain name of your host. But make this configurable because if there 31 | * are two nodes running on the host they must have distinct IDs. 32 | * NodeSettings will reject attempts to use a non-qualified name. 33 | *
  • 34 | *
  • The realm should be the non-first part of host ID
  • 35 | *
  • Use your own vendor ID. Don't make up a value. Apply for one at IANA (it is free). 36 | * Check if your organization does not already have one 37 | * (www.iana.org/assignments/enterprise-numbers). 38 | * Do not make this configurable. 39 | *
  • You must add the applications to the capability before constructing 40 | * a NodeSettings. 41 | *
  • 42 | *
  • You will normally specify 3868 as the port even for "client programs". 43 | * Remember to make this configurable so two nodes can run on the same host. 44 | *
  • 45 | *
  • The product name should be a stable name, and only change if the product 46 | * changes and not if some marketing guy invents a new name. 47 | * Do not make this configurable. 48 | *
  • 49 | *
  • Firmware revision is up to you to decide. You should increment this for each release.
  • 50 | *
51 | */ 52 | public class NodeSettings { 53 | private String host_id; 54 | private String realm; 55 | private int vendor_id; 56 | private Capability capabilities; 57 | private int port; 58 | private String product_name; 59 | private int firmware_revision; 60 | private long watchdog_interval; 61 | private long idle_close_timeout; 62 | private Boolean use_tcp; 63 | private Boolean use_sctp; 64 | private PortRange port_range; 65 | 66 | /** 67 | * A port range 68 | */ 69 | public static class PortRange { 70 | public int min; 71 | public int max; 72 | public PortRange(int min, int max) throws InvalidSettingException 73 | { 74 | if(min<=0 || min>max || max>=65536) 75 | throw new InvalidSettingException("Invalid port range, 0 < min <= max < 65536"); 76 | this.min = min; 77 | this.max = max; 78 | } 79 | }; 80 | 81 | /** 82 | * Constructor for NodeSettings. 83 | * @param host_id The Diameter host identity. 84 | * @param vendor_id Your IANA-assigned "SMI Network Management Private Enterprise Code" 85 | * @param realm The Diameter realm. 86 | * @param capabilities The capabilities of this node. 87 | * @param port The port to listen on. Use 0 to specify that this node should not listen for incoming connections. 88 | * @param product_name The name of this product eg. "FooBar gateway" 89 | * @param firmware_revision The "firmware" revision ie. the version of you product. Use 0 to specify none. 90 | */ 91 | public NodeSettings(String host_id, String realm, 92 | int vendor_id, 93 | Capability capabilities, 94 | int port, 95 | String product_name, int firmware_revision) 96 | throws InvalidSettingException 97 | { 98 | int i; 99 | if(host_id==null) 100 | throw new InvalidSettingException("null host_id"); 101 | i = host_id.indexOf('.'); 102 | if(i==-1) 103 | throw new InvalidSettingException("host_id must contains at least 2 dots"); 104 | if(host_id.indexOf('.',i+1)==-1) 105 | throw new InvalidSettingException("host_id must contains at least 2 dots"); 106 | this.host_id = host_id; 107 | 108 | i = realm.indexOf('.'); 109 | if(i==-1) 110 | throw new InvalidSettingException("realm must contain at least 1 dot"); 111 | this.realm = realm; 112 | 113 | if(vendor_id==0) 114 | throw new InvalidSettingException("vendor_id must not be non-zero. (It must be your IANA-assigned \"SMI Network Management Private Enterprise Code\". See http://www.iana.org/assignments/enterprise-numbers)"); 115 | this.vendor_id = vendor_id; 116 | 117 | if(capabilities.isEmpty()) 118 | throw new InvalidSettingException("Capabilities must be non-empty"); 119 | this.capabilities = capabilities; 120 | if(port<0 || port>65535) 121 | throw new InvalidSettingException("listen-port must be 0..65535"); 122 | this.port = port; 123 | 124 | if(product_name==null) 125 | throw new InvalidSettingException("product-name cannot be null"); 126 | this.product_name = product_name; 127 | this.firmware_revision = firmware_revision; 128 | this.watchdog_interval = 30*1000; 129 | this.idle_close_timeout = 7*24*3600*1000; 130 | } 131 | 132 | /**Returns the configured host ID*/ 133 | public String hostId() { 134 | return host_id; 135 | } 136 | 137 | /**Returns the configured realm*/ 138 | public String realm() { 139 | return realm; 140 | } 141 | 142 | /**Returns the configured vendor ID*/ 143 | public int vendorId() { 144 | return vendor_id; 145 | } 146 | 147 | /**Returns the configured capabilities*/ 148 | public Capability capabilities() { 149 | return capabilities; 150 | } 151 | 152 | /**Returns the configured listen port. 0 if not listening*/ 153 | public int port() { 154 | return port; 155 | } 156 | 157 | /**Returns the product name*/ 158 | public String productName() { 159 | return product_name; 160 | } 161 | 162 | /**Returns the firmware revision*/ 163 | public int firmwareRevision() { 164 | return firmware_revision; 165 | } 166 | 167 | /**Returns the desired DWR interval (in milliseconds). The default 168 | * interval is 30 seconds as per RFC3539 section 3.4.1 169 | * @since 0.9.3 170 | */ 171 | public long watchdogInterval() { 172 | return watchdog_interval; 173 | } 174 | 175 | /**Sets the desired DWR/DWA interval. The default interval is 30 seconds 176 | * as per RFC3539 section 3.4.1 177 | * @param interval DWR interval in milliseconds 178 | * @throws InvalidSettingException If the interval is less than 6000 milliseconds 179 | * @since 0.9.3 180 | */ 181 | public void setWatchdogInterval(long interval) throws InvalidSettingException { 182 | if(interval<6*1000) 183 | throw new InvalidSettingException("watchdog interval must be at least 6 seconds. RFC3539 section 3.4.1 item 1"); 184 | this.watchdog_interval = interval; 185 | } 186 | 187 | /**Returns the idle timeout (in milliseconds) 188 | * @since 0.9.3 189 | */ 190 | public long idleTimeout() { 191 | return idle_close_timeout; 192 | } 193 | 194 | /**Sets the idle close timeout. The default idle timeout is 7 days, 195 | * after which the connection will closed with reason='busy' unless 196 | * there has been non-watchdog traffic on the connection. 197 | * @param timeout Timeout in milliseconds. If 0 then idle timeout is disabled and connections will be kept open. 198 | * @throws InvalidSettingException If timeout is negative. 199 | * @since 0.9.3 200 | */ 201 | public void setIdleTimeout(long timeout) throws InvalidSettingException { 202 | if(timeout<0) 203 | throw new InvalidSettingException("idle timeout cannot be negative"); 204 | this.idle_close_timeout = timeout; 205 | } 206 | 207 | /**Returns the setting for using TCP. 208 | * @return A boolean object, or null if not set. 209 | * @since 0.9.5 210 | */ 211 | public Boolean useTCP() { 212 | return use_tcp; 213 | } 214 | /** Change the setting for using TCP 215 | * Sets the setting to the spciefied value, which can be null. 216 | * When the setting is: 217 |
218 |
true
then the stack will create a TCP sub-node.
219 |
false
then the stack will not create a TCP sub-node.
220 |
null
then the stack will use the a property instead (see {@link Node} for details}.
221 |
222 | * @param use_tcp New TCP use setting. Can be null. 223 | * @since 0.9.5 224 | */ 225 | public void setUseTCP(Boolean use_tcp) { 226 | this.use_tcp = use_tcp; 227 | } 228 | 229 | /**Returns the setting for using SCTP. 230 | * @return A boolean object, or null if not set. 231 | * @since 0.9.5 232 | */ 233 | public Boolean useSCTP() { 234 | return use_sctp; 235 | } 236 | /** Change the setting for using SCTP 237 | * Sets the setting to the spciefied value, which can be null. 238 | * When the setting is: 239 |
240 |
true
then the stack will create a SCTP sub-node.
241 |
false
then the stack will not create a SCTP sub-node.
242 |
null
then the stack will use the a property instead (see {@link Node} for details}.
243 |
244 | * @param use_sctp New SCTP use setting. Can be null. 245 | * @since 0.9.5 246 | */ 247 | public void setUseSCTP(Boolean use_sctp) { 248 | this.use_sctp = use_sctp; 249 | } 250 | 251 | /** 252 | * Set the source port range for outgoing TCP connections 253 | * If the source port range is no tset (default) then the stack will 254 | * use an ephemeral source port. Specifying the source prot range can be 255 | * useful in some environments where there are restictive NAT issues, 256 | * or where the firewall administrator cannot accomodate a free soruce port range. 257 | * @param port_range A source port range to use for initiating outgoing TCP connections. 258 | * @since 0.9.6.8 259 | */ 260 | public void TCPPortRange(PortRange port_range) throws InvalidSettingException { 261 | this.port_range = port_range; 262 | } 263 | /** 264 | * Set the source port range for outgoing TCP connections 265 | * @see #TCPPortRange(PortRange) 266 | * @since 0.9.6.8 267 | */ 268 | public void TCPPortRange(int min, int max) throws InvalidSettingException { 269 | port_range = new PortRange(min,max); 270 | } 271 | /** 272 | * Get the source port range for outgoing TCP connections 273 | * @return A PortRange object, or null 274 | * @since 0.9.6.8 275 | */ 276 | public PortRange TCPPortRange() { 277 | return port_range; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/SessionManager.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.session; 2 | import dk.i1.diameter.*; 3 | import dk.i1.diameter.node.*; 4 | import java.util.*; 5 | import java.util.logging.Logger; 6 | import java.util.logging.Level; 7 | 8 | /** 9 | * A go-between sessions and NodeManager. 10 | * The SessionManager keeps track of outstanding requests and dispatches 11 | * answers to the sessions. It also keeps track of the timeouts in the 12 | * sessions. 13 | *

14 | * SessionManager instances logs with the name "dk.i1.diameter.session", so 15 | * you can get detailed logging (including hex-dumps of incoming and outgoing 16 | * packets) by putting "dk.i1.diameter.session.level = ALL" into your 17 | * log.properties file (or equivalent) 18 | */ 19 | public class SessionManager extends NodeManager { 20 | private static class SessionAndTimeout { 21 | public Session session; 22 | public long timeout; 23 | public boolean deleted; 24 | public SessionAndTimeout(Session session) { 25 | this.session = session; 26 | this.timeout = session.calcNextTimeout(); 27 | this.deleted = false; 28 | } 29 | } 30 | private Map map_session; 31 | private Peer peers[]; 32 | private Thread timer_thread; 33 | private long earliest_timeout; 34 | private boolean stop; 35 | Logger logger; 36 | 37 | /** 38 | * Constructor for SessionManager. 39 | * @param settings The node settings 40 | * @param peers The default set of peers. If a subclass overrides the 41 | * peers() methods then this parameter can be null. 42 | */ 43 | public SessionManager(NodeSettings settings, Peer peers[]) throws InvalidSettingException 44 | { 45 | super(settings); 46 | if(settings.port()==0) 47 | throw new InvalidSettingException("If you have sessions then you must allow inbound connections"); 48 | map_session = new HashMap(); 49 | this.peers = peers; 50 | earliest_timeout = Long.MAX_VALUE; 51 | stop = false; 52 | logger = Logger.getLogger("dk.i1.diameter.session"); 53 | } 54 | 55 | /** 56 | * Start the session manager. 57 | * The SessionManager must be started before it can be used by sessions. 58 | */ 59 | public void start() throws java.io.IOException, UnsupportedTransportProtocolException { 60 | logger.log(Level.FINE,"Starting session manager"); 61 | super.start(); 62 | timer_thread = new TimerThread(); 63 | timer_thread.setDaemon(true); 64 | timer_thread.start(); 65 | for(Peer p : peers) { 66 | super.node().initiateConnection(p,true); 67 | } 68 | } 69 | /** 70 | * Stop the SessionManager. 71 | * @param grace_time Maximum time (milliseconds) to wait for connections to close gracefully. 72 | * @since grace_time parameter introduced in 0.9.3 73 | */ 74 | public void stop(long grace_time) { 75 | logger.log(Level.FINE,"Stopping session manager"); 76 | super.stop(grace_time); 77 | synchronized(map_session) { 78 | stop = true; 79 | map_session.notify(); 80 | } 81 | try { 82 | timer_thread.join(); 83 | } catch(java.lang.InterruptedException e) {} 84 | logger.log(Level.FINE,"Session manager stopped"); 85 | } 86 | 87 | 88 | /** 89 | * Handle incoming request. 90 | * Examines the Session-Id AVP and dispatches the request to the session. 91 | */ 92 | protected void handleRequest(Message request, ConnectionKey connkey, Peer peer) { 93 | logger.log(Level.FINE,"Handling request, command_code="+request.hdr.command_code); 94 | //todo: verify that destination-host is us 95 | Message answer = new Message(); 96 | answer.prepareResponse(request); 97 | 98 | String session_id = extractSessionId(request); 99 | if(session_id==null) { 100 | logger.log(Level.FINE,"Cannot handle request - no Session-Id AVP in request"); 101 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE, ProtocolConstants.DIAMETER_RESULT_MISSING_AVP)); 102 | node().addOurHostAndRealm(answer); 103 | answer.add(new AVP_Grouped(ProtocolConstants.DI_FAILED_AVP,new AVP[]{new AVP_UTF8String(ProtocolConstants.DI_SESSION_ID,"")})); 104 | Utils.copyProxyInfo(request,answer); 105 | Utils.setMandatory_RFC3588(answer); 106 | try { 107 | answer(answer,connkey); 108 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) {} 109 | return; 110 | } 111 | Session s = findSession(session_id); 112 | if(s==null) { 113 | logger.log(Level.FINE,"Cannot handle request - Session-Id '"+session_id+" does not denote a known session"); 114 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE, ProtocolConstants.DIAMETER_RESULT_UNKNOWN_SESSION_ID)); 115 | node().addOurHostAndRealm(answer); 116 | Utils.copyProxyInfo(request,answer); 117 | Utils.setMandatory_RFC3588(answer); 118 | try { 119 | answer(answer,connkey); 120 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) {} 121 | return; 122 | } 123 | int result_code = s.handleRequest(request); 124 | answer.add(new AVP_Unsigned32(ProtocolConstants.DI_RESULT_CODE, result_code)); 125 | node().addOurHostAndRealm(answer); 126 | Utils.copyProxyInfo(request,answer); 127 | Utils.setMandatory_RFC3588(answer); 128 | try { 129 | answer(answer,connkey); 130 | } catch(dk.i1.diameter.node.NotAnAnswerException ex) {} 131 | } 132 | 133 | private static class RequestState { 134 | public int command_code; 135 | public Object state; 136 | public Session session; 137 | } 138 | 139 | /** 140 | * Handle an answer to an outstanding request. 141 | * Dispatches the answer to the corresponding session by calling 142 | * either Session.handleAnswer() or Session.handleNonAnswer() 143 | */ 144 | protected void handleAnswer(Message answer, ConnectionKey answer_connkey, Object state) { 145 | if(answer!=null) 146 | logger.log(Level.FINE,"Handling answer, command_code="+answer.hdr.command_code); 147 | else 148 | logger.log(Level.FINE,"Handling non-answer"); 149 | Session s; 150 | String session_id = extractSessionId(answer); 151 | logger.log(Level.FINEST,"session-id="+session_id); 152 | if(session_id!=null) { 153 | s = findSession(session_id); 154 | } else { 155 | s = ((RequestState)state).session; 156 | } 157 | if(s==null) { 158 | logger.log(Level.FINE,"Session '" + session_id +"' not found"); 159 | return; 160 | } 161 | logger.log(Level.FINE,"Found session, dispatching (non-)answer to it"); 162 | 163 | if(answer!=null) 164 | s.handleAnswer(answer,((RequestState)state).state); 165 | else 166 | s.handleNonAnswer(((RequestState)state).command_code,((RequestState)state).state); 167 | } 168 | 169 | /** 170 | * Send a request for a session. 171 | * Sessions must use this method and not NodeManager.sendRequest(), as 172 | * the SessionManager must keep track of outstanding requests. 173 | * Note that the session's handleAnswer() method may get called before 174 | * this method returns. 175 | * @param request The request to send- 176 | * @param session The session the request is sent on behalf of. 177 | * @param state A state object that will be given in the {@link Session#handleAnswer} or {@link Session#handleNonAnswer} call. 178 | */ 179 | public void sendRequest(Message request, Session session, Object state) throws NotRoutableException, NotARequestException { 180 | logger.log(Level.FINE,"Sending request (command_code="+request.hdr.command_code+") for session "+session.sessionId()); 181 | RequestState rs = new RequestState(); 182 | rs.command_code = request.hdr.command_code; 183 | rs.state = state; 184 | rs.session = session; 185 | sendRequest(request,peers(request),rs); 186 | } 187 | 188 | /** 189 | * Retrieve the default set of peers. 190 | * @return The set of peers specified in the constructor. 191 | */ 192 | public Peer[] peers() { 193 | return peers; 194 | } 195 | /** 196 | * Retrieve a set of peers suitable for the specified request. 197 | * A subclass can override this method to implement more 198 | * intelligent peer selection. 199 | * @param request The request that will be sent to one of the returned peers. 200 | * @return a set of suitable peers. 201 | */ 202 | public Peer[] peers(Message request) { 203 | return peers; 204 | } 205 | 206 | /** 207 | * Register a session for management. 208 | * The BaseSession class calls this method when appropriate 209 | * @param s The Session to be registered. 210 | */ 211 | public void register(Session s) { 212 | SessionAndTimeout sat = new SessionAndTimeout(s); 213 | synchronized(map_session) { 214 | map_session.put(s.sessionId(),sat); 215 | if(sat.timeout it=msg.iterator(ProtocolConstants.DI_SESSION_ID); 265 | if(!it.hasNext()) 266 | return null; 267 | return new AVP_UTF8String(it.next()).queryValue(); 268 | } 269 | 270 | private class TimerThread extends Thread { 271 | public TimerThread() { 272 | super("SessionManager timer thread"); 273 | } 274 | public void run() { 275 | synchronized(map_session) { 276 | while(!stop) { 277 | long now=System.currentTimeMillis(); 278 | earliest_timeout = Long.MAX_VALUE; 279 | for(Iterator> it = map_session.entrySet().iterator(); 280 | it.hasNext() 281 | ;) 282 | { 283 | Map.Entry e = it.next(); 284 | if(e.getValue().deleted) { 285 | it.remove(); 286 | continue; 287 | } 288 | Session session = e.getValue().session; 289 | if(e.getValue().timeoutnow) { 299 | if(earliest_timeout==Long.MAX_VALUE) 300 | map_session.wait(); 301 | else 302 | map_session.wait(earliest_timeout-now); 303 | } 304 | } catch(java.lang.InterruptedException e) { 305 | } 306 | } 307 | } 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /dk/i1/diameter/Message.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter; 2 | import java.util.*; 3 | 4 | /** 5 | * A Diameter Message. 6 | * The Message is a container for the {@link MessageHeader} and the {@link AVP}s. 7 | * It supports converting to/from the on-the-wire format, and 8 | * manipulating the AVPs. The class is lean and mean, and does as little 9 | * checking as possible. 10 | *

Example of building a Message: 11 |

 12 | Message msg = new Message();
 13 | msg.hdr.application_id = ProtocolConstants.DIAMETER_APPLICATION_ACCOUNTING;
 14 | msg.hdr.command_code = ProtocolConstants.DIAMETER_COMMAND_ACCOUNTING;
 15 | msg.hdr.setRequest(true);
 16 | msg.hdr.setProxiable(true);
 17 | //Add AVPs
 18 | ...
 19 | msg.add(new AVP_UTF8String(ProtocolConstants.DI_USER_NAME,"user@example.net"));
 20 | msg.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_INPUT_OCTETS,36758373691049));
 21 | ...
 22 | 
23 | * Example of processing a message: 24 |
 25 | Message msg ...;
 26 | for(AVP avp : msg.subset(ProtocolConstants.DI_FRAMED_IP_ADDRESS)) {
 27 |     try {
 28 |         InetAddress address = new AVP_Address(avp).queryAddress();
 29 | 	..do something useful with the address...
 30 |     } catch(InvalidAVPLengthException ex) {
 31 |         .. handle when peer sends garbage
 32 |     } catch(InvalidAddressTypeException ex) {
 33 |         .. handle when peer sends garbage
 34 |     }
 35 | }
 36 | AVP avp_reply_message = msg.find(ProtocolConstants.DI_REPLY_MESSAGE);
 37 | if(avp!=null) {
 38 |     ..do something sensible with reply-message
 39 | }
 40 | 
41 | */ 42 | public class Message { 43 | /** The message header*/ 44 | public MessageHeader hdr; 45 | private ArrayList avp; 46 | 47 | /** The default constructor. The header is initialized to default 48 | * values and the AVP list will be empty 49 | */ 50 | public Message() { 51 | hdr = new MessageHeader(); 52 | avp = new ArrayList(); 53 | } 54 | /** 55 | * Construct a message with a specific header. The AVP list will be empty. 56 | * @param header The message header to use instead of a default one. 57 | */ 58 | public Message(MessageHeader header) { 59 | hdr = new MessageHeader(header); 60 | avp = new ArrayList(); 61 | } 62 | /** 63 | * Copy-constructor. 64 | * Implements a deep copy. 65 | */ 66 | public Message(Message msg) { 67 | this(msg.hdr); 68 | for(AVP a : msg.avp) 69 | avp.add(new AVP(a)); 70 | } 71 | 72 | /** 73 | * Calculate the size of the message in on-the-wire format 74 | * @return The number of bytes the message will use on-the-wire. 75 | */ 76 | public int encodeSize() { 77 | int sz=0; 78 | sz += hdr.encodeSize(); 79 | for(AVP a : avp) { 80 | sz += a.encodeSize(); 81 | } 82 | return sz; 83 | } 84 | /** 85 | * Encode the message in on-the-wire format to the specified byte array. 86 | * @param b The byte array which must be large enough. 87 | */ 88 | public void encode(byte b[]) { 89 | int sz = encodeSize(); 90 | int offset=0; 91 | offset += hdr.encode(b,offset,sz); 92 | for(AVP a : avp) { 93 | offset += a.encode(b,offset); 94 | } 95 | } 96 | /** 97 | * Encode the message to on-the-wire format 98 | * @return A on-the-wire message byte array 99 | */ 100 | public byte[] encode() { 101 | int sz = encodeSize(); 102 | byte b[] = new byte[sz]; 103 | int offset=0; 104 | offset += hdr.encode(b,offset,sz); 105 | for(AVP a : avp) { 106 | offset += a.encode(b,offset); 107 | } 108 | return b; 109 | } 110 | 111 | /** 112 | * Determine the complete size of the message from a on-the-wire byte array. 113 | * There must be at least 4 bytes available in the array. 114 | * @param b The byte array 115 | * @param offset The offset into the byte array where the message is supposed to start. 116 | * @return The size (in bytes) of the message 117 | */ 118 | public static int decodeSize(byte b[], int offset) { 119 | int v_ml = packunpack.unpack32(b,offset); 120 | int v = (v_ml>>24)&0xff; 121 | int ml = v_ml & 0x00FFFFFF; 122 | if(v!=1 || ml<20 || (ml%4)!=0) 123 | return 4; //will cause decode() to fail 124 | return ml; 125 | } 126 | 127 | /** The decode status from {@link Message#decode} */ 128 | public enum decode_status { 129 | /** A complete Diameter message was successfully decoded*/ 130 | decoded, 131 | /** The buffer appears so far to contain a valid message byte there is not enough bytes for it*/ 132 | not_enough, 133 | /** The buffer cannot possibly contain a Diameter message*/ 134 | garbage 135 | } 136 | 137 | /** 138 | * Decode a message from on-the-wire format. 139 | * Implemented as return decode(b,0,b.length) 140 | * @param b A byte array which consists of exacly 1 message. Superfluous bytes are not permitted. 141 | * @return The result for the decode operation. 142 | */ 143 | public decode_status decode(byte b[]) { 144 | return decode(b,0,b.length); 145 | } 146 | 147 | /** 148 | * Decode a message from on-the-wire format. 149 | * The message is checked to be in valid format and the VPs to be of 150 | * the correct length etc. Invalid/reserved bits are not checked. 151 | * @param b A byte array possibly containing a Diameter message 152 | * @param offset Offset into the array where decoding should start 153 | * @param bytes The bytes to try to decode 154 | * @return The result for the decode operation. 155 | */ 156 | public decode_status decode(byte b[], int offset, int bytes) { 157 | if(bytes<1) 158 | return decode_status.not_enough; 159 | if(packunpack.unpack8(b,offset)!=1) 160 | return decode_status.garbage; 161 | if(bytes<4) 162 | return decode_status.not_enough; 163 | int sz = decodeSize(b,offset); 164 | if((sz&3)!=0) 165 | return decode_status.garbage; 166 | if(sz<20) 167 | return decode_status.garbage; 168 | if(bytes<20) 169 | return decode_status.not_enough; 170 | if(sz==-1) 171 | return decode_status.garbage; 172 | if(bytes new_avps = new ArrayList(estimated_avp_count); 182 | while(bytes_left>0) { 183 | if(bytes_left<8) 184 | return decode_status.garbage; 185 | int avp_sz = AVP.decodeSize(b,offset,bytes_left); 186 | if(avp_sz==0) 187 | return decode_status.garbage; 188 | if(avp_sz>bytes_left) 189 | return decode_status.garbage; 190 | 191 | AVP new_avp = new AVP(); 192 | if(!new_avp.decode(b,offset,avp_sz)) 193 | return decode_status.garbage; 194 | new_avps.add(new_avp); 195 | offset += avp_sz; 196 | bytes_left -= avp_sz; 197 | } 198 | if(bytes_left!=0) 199 | return decode_status.garbage; 200 | 201 | avp = new_avps; 202 | return decode_status.decoded; 203 | } 204 | 205 | /**Return the number of AVPs in the message*/ 206 | public int size() { return avp.size(); } 207 | /**Ensure that ther is room for at least he specified number of AVPs*/ 208 | public void ensureCapacity(int minCapacity) { avp.ensureCapacity(minCapacity); } 209 | /**Gets the AVP at the specified index (0-based)*/ 210 | public AVP get(int index) { return new AVP(avp.get(index)); } 211 | /**Removes all AVPs from the message*/ 212 | public void clear() { avp.clear(); } 213 | /**Adds an AVP at the end of the AVP list*/ 214 | public void add(AVP avp) { this.avp.add(avp); } 215 | /**Inserts an AVP at the specified posistion (0-based)*/ 216 | public void add(int index, AVP avp) { this.avp.add(index,avp); } 217 | /**Removes the AVP at the specified position (0-based)*/ 218 | public void remove(int index) { avp.remove(index); } 219 | 220 | 221 | private class AVPIterator implements Iterator { 222 | private ListIterator i; 223 | private int code; 224 | private int vendor_id; 225 | AVPIterator(ListIterator i, int code, int vendor_id) { 226 | this.i = i; 227 | this.code = code; 228 | this.vendor_id = vendor_id; 229 | } 230 | public void remove() { 231 | i.remove(); 232 | } 233 | public boolean hasNext() { 234 | while(i.hasNext()) { 235 | AVP a=i.next(); 236 | if(a.code==code && 237 | (vendor_id==0 || a.vendor_id==vendor_id)) 238 | { 239 | i.previous(); 240 | return true; 241 | } 242 | } 243 | return false; 244 | } 245 | public AVP next() { 246 | return i.next(); 247 | } 248 | } 249 | 250 | /**Returns an Iterable for the AVPs*/ 251 | public Iterable avps() { return avp; } 252 | /**Returns an iterator for the AVP list*/ 253 | public Iterator iterator() { return avp.iterator(); } 254 | /**Returns an iterator for the AVPs with the specified code*/ 255 | public Iterator iterator(int code) { return iterator(code,0); } 256 | /**Returns an iterator for the AVPs with the specified code and vendor id*/ 257 | public Iterator iterator(int code, int vendor_id) { 258 | return new AVPIterator(avp.listIterator(),code,vendor_id); 259 | } 260 | 261 | /** 262 | * Prepare a response the the supplied request. 263 | * Implemented as hdr.prepareResponse(request.hdr); 264 | * @see MessageHeader#prepareResponse(MessageHeader) 265 | */ 266 | public void prepareResponse(Message request) { 267 | hdr.prepareResponse(request.hdr); 268 | } 269 | 270 | /** 271 | * Prepare an answer from the specified request header. 272 | * This is identical to prepareResponse(). 273 | * @since 0.9.3 274 | */ 275 | public void prepareAnswer(Message request) { 276 | prepareResponse(request); 277 | } 278 | 279 | private class Subset implements Iterable { 280 | Message msg; 281 | int code; 282 | int vendor_id; 283 | Subset(Message msg, int code, int vendor_id) { 284 | this.msg = msg; 285 | this.code = code; 286 | this.vendor_id = vendor_id; 287 | } 288 | public Iterator iterator() { 289 | return msg.iterator(code,vendor_id); 290 | } 291 | } 292 | 293 | /** 294 | * Returns a iterable subset of the AVPs. 295 | * Implemented as
return subset(code,0);
296 | */ 297 | public Iterable subset(int code) { 298 | return subset(code,0); 299 | } 300 | /** 301 | * Returns a iterable subset of the AVPs. 302 | * This is mainly useful for the new-style foreach statement as in 303 | *
304 | 	 * for(AVP avp : message.subset(...something...)) {
305 | 	 *    //process the AVP
306 | 	 * }
307 | 	 * 
308 | * @param code The AVP code 309 | * @return An iterable subset 310 | */ 311 | public Iterable subset(int code, int vendor_id) { 312 | return new Subset(this,code,vendor_id); 313 | } 314 | 315 | /** 316 | * Finds an AVP with the specified code. 317 | * Implemented as find(code,0); 318 | */ 319 | public AVP find(int code) { 320 | return find(code,0); 321 | } 322 | /** 323 | * Finds an AVP with the specified code/vendor-id. 324 | * Returns an AVP with the specified code (and vendor-id) if any. If 325 | * there are no AVPs in this message with the specified code/vendor-id 326 | * then null is returned. 327 | * It is unspecified which AVP is returned if there are multiple matches. 328 | * @param code AVP code 329 | * @param vendor_id Vendor-ID. Use 0 to specify none. 330 | * @return AP with the specified code/vendor-id. Null if not found. 331 | */ 332 | public AVP find(int code, int vendor_id) { 333 | for(AVP a:avp) { 334 | if(a.code==code && a.vendor_id==vendor_id) 335 | return a; 336 | } 337 | return null; 338 | } 339 | 340 | int find_first(int code) { 341 | int i=0; 342 | for(AVP a:avp) { 343 | if(a.code==code) 344 | return i; 345 | i++; 346 | } 347 | return -1; 348 | } 349 | int count(int code) { 350 | int i=0; 351 | for(AVP a:avp) { 352 | if(a.code==code) 353 | i++; 354 | } 355 | return i; 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /dk/i1/diameter/session/ACHandler.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.session; 2 | import dk.i1.diameter.*; 3 | import java.util.*; 4 | import java.util.logging.Logger; 5 | import java.util.logging.Level; 6 | 7 | /** 8 | * A utility class for dealing with accounting. 9 | * It supports sub-sessions, interim accounting and other common stuff. 10 | * It can be used for incorporating into session classes. The session must dispatch ACAs to it. 11 | * The class uses the sub-session 0 for the session itself and it is always present. 12 | * It is the responsibility of the user to update the the usage data in the 13 | * SubSession instances and/or override the collectACRInfo() method. 14 | * Acct-Realtime-Required semantics are not directly supported. 15 | */ 16 | public class ACHandler { 17 | private BaseSession base_session; 18 | private long subsession_sequencer; 19 | private int accounting_record_number; 20 | /**The acct-multi-session-id to include in ACRs, if any*/ 21 | public String acct_multi_session_id; 22 | /**The acct-application-id to include in ACRs. If not set, then collectACRInfo() must be overridden to add a vendor-specific-application AVP*/ 23 | public Integer acct_application_id; 24 | 25 | /** 26 | * A collection of data belonging to a (sub-)session. 27 | * The user of the ACHandler class is supposed to update the acct_* 28 | * fields either before calling {@link ACHandler#handleTimeout}, {@link ACHandler#startSubSession}, 29 | * {@link ACHandler#stopSubSession}, {@link ACHandler#stopSession} and {@link ACHandler#sendEvent}; or whenever new 30 | * usage information is received for the user. 31 | */ 32 | public static class SubSession { 33 | final long subsession_id; 34 | boolean start_sent; 35 | public long interim_interval; 36 | long next_interim; 37 | int most_recent_record_number; 38 | /** The accounting session-time, in milliseconds. Can be null */ 39 | public Long acct_session_time; 40 | /** The number of octets received from the user. Can be null */ 41 | public Long acct_input_octets; 42 | /** The number of octets sent to the user. can be null */ 43 | public Long acct_output_octets; 44 | /** The number of packets received from the user. Can be null */ 45 | public Long acct_input_packets; 46 | /** The number of packets sent to the user. can be null */ 47 | public Long acct_output_packets; 48 | 49 | SubSession(long ss_id) { 50 | subsession_id = ss_id; 51 | interim_interval = Long.MAX_VALUE; 52 | next_interim = Long.MAX_VALUE; 53 | most_recent_record_number = -1; 54 | } 55 | } 56 | private Map subsessions; 57 | 58 | /** 59 | * Constructor for ACHandler 60 | * @param base_session The BaseSession (or subclass thereof) for which 61 | * the accounting should be produced. 62 | */ 63 | public ACHandler(BaseSession base_session) { 64 | this.base_session = base_session; 65 | accounting_record_number = 0; 66 | subsessions = new HashMap(); 67 | subsession_sequencer = 0; 68 | subsessions.put(subsession_sequencer,new SubSession(subsession_sequencer++)); 69 | } 70 | /** 71 | * Calculate the next time that handleTimeouts() should be called. 72 | * The timeout is calcualted based on the earliest timeout of interim 73 | * for any of the subsessions 74 | */ 75 | public long calcNextTimeout() { 76 | long t = Long.MAX_VALUE; 77 | for(Map.Entry e : subsessions.entrySet()) { 78 | SubSession ss = e.getValue(); 79 | t = Math.min(t,ss.next_interim); 80 | } 81 | return t; 82 | } 83 | /** 84 | * Process timeouts, if any. 85 | * Accounting-interim requests may get sent. 86 | */ 87 | public void handleTimeout() { 88 | long now = System.currentTimeMillis(); 89 | for(Map.Entry e : subsessions.entrySet()) { 90 | SubSession ss = e.getValue(); 91 | if(ss.next_interim<=now) { 92 | sendInterim(ss); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Creates a sub-session. 99 | * Creates a subsession with a unique sub-session-id. It is the 100 | * responsibility of the caller call startSubSession() afterward. 101 | * The Sub-session is not automatically started. 102 | * @return ID of the sub-session 103 | */ 104 | public long createSubSession() { 105 | SubSession ss = new SubSession(subsession_sequencer++); 106 | subsessions.put(ss.subsession_id,ss); 107 | return ss.subsession_id; 108 | } 109 | /** 110 | * Retrieve a sub-session by id 111 | * @param subsession_id The sub-session id 112 | * @return The sub-session, or null if not found. 113 | */ 114 | public SubSession subSession(long subsession_id) { 115 | return subsessions.get(subsession_id); 116 | } 117 | /** 118 | * Start sub-session accounting for the specified sub-session. 119 | * This will result in the ACR start-record being sent. 120 | * @param subsession_id The sub-session id 121 | */ 122 | public void startSubSession(long subsession_id) { 123 | if(subsession_id==0) return; 124 | SubSession ss = subSession(subsession_id); 125 | if(ss==null) return; 126 | if(ss.start_sent) return; //already started 127 | sendStart(ss); 128 | } 129 | /** 130 | * Stop a sub-session. 131 | * The sub-session is stopped (accounting-stop ACR will be generated) 132 | * and the sub-session will be removed. 133 | * @param subsession_id The sub-session id 134 | */ 135 | public void stopSubSession(long subsession_id) { 136 | if(subsession_id==0) return; 137 | SubSession ss = subSession(subsession_id); 138 | if(ss==null) return; 139 | sendStop(ss); 140 | subsessions.remove(ss.subsession_id); 141 | } 142 | 143 | /** 144 | * Start accounting for the session 145 | * This will result in the ACR start-record being sent. 146 | */ 147 | public void startSession() { 148 | SubSession ss = subSession(0); 149 | if(ss.start_sent) return; 150 | sendStart(ss); 151 | } 152 | /** 153 | * Stop accounting. 154 | * Stop accounting by sending ACRs (stop records) for all sub-sessions 155 | * and deleting them, and then finally sending a ACR stop-record for the 156 | * whole session. 157 | */ 158 | public void stopSession() { 159 | //Sending stop records for the sub sessions is not strictly needed but nice anyway 160 | for(Map.Entry e : subsessions.entrySet()) { 161 | if(e.getValue().subsession_id==0) continue; 162 | sendStop(e.getValue()); 163 | } 164 | SubSession ss = subSession(0); 165 | sendStop(ss); 166 | subsessions.clear(); 167 | } 168 | 169 | 170 | /** 171 | * Send an event record for the whole session. 172 | * Implemented as sendEvent(0,null) 173 | */ 174 | public void sendEvent() { 175 | sendEvent(0,null); 176 | } 177 | /** 178 | * Send an event record for the whole session with an additional set of AVPs 179 | * Implemented as sendEvent(0,null) 180 | */ 181 | public void sendEvent(AVP avps[]) { 182 | sendEvent(0,avps); 183 | } 184 | /** 185 | * Send an event record for the sub-session with an additional set of AVPs 186 | * Implemented as sendEvent(subsession_id,null) 187 | */ 188 | public void sendEvent(long subsession_id) { 189 | sendEvent(subsession_id,null); 190 | } 191 | /** 192 | * Send an event record for the sub-session with an additional set of AVPs 193 | * collectACR() will be called and the AVPs will then be added to the ACR, and then sent. 194 | */ 195 | public void sendEvent(long subsession_id, AVP avps[]) { 196 | SubSession ss = subSession(0); 197 | if(ss==null) return; //todo: exception 198 | sendEvent(ss,avps); 199 | } 200 | 201 | private void sendStart(SubSession ss) { 202 | sendACR(makeACR(ss,ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE_START_RECORD)); 203 | if(ss.interim_interval!=Long.MAX_VALUE) 204 | ss.next_interim = System.currentTimeMillis()+ss.interim_interval; 205 | else 206 | ss.next_interim = Long.MAX_VALUE; 207 | } 208 | private void sendInterim(SubSession ss) { 209 | sendACR(makeACR(ss,ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE_INTERIM_RECORD)); 210 | if(ss.interim_interval!=Long.MAX_VALUE) 211 | ss.next_interim = System.currentTimeMillis()+ss.interim_interval; 212 | else 213 | ss.next_interim = Long.MAX_VALUE; 214 | } 215 | private void sendStop(SubSession ss) { 216 | sendACR(makeACR(ss,ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE_STOP_RECORD)); 217 | } 218 | private void sendEvent(SubSession ss, AVP avps[]) { 219 | Message acr = makeACR(ss,ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE_EVENT_RECORD); 220 | if(avps!=null) { 221 | for(AVP a : avps) 222 | acr.add(a); 223 | } 224 | sendACR(acr); 225 | } 226 | 227 | private Message makeACR(SubSession ss, int record_type) { 228 | Message acr = new Message(); 229 | acr.hdr.setRequest(true); 230 | acr.hdr.setProxiable(true); 231 | acr.hdr.application_id = base_session.authAppId(); 232 | acr.hdr.command_code = ProtocolConstants.DIAMETER_COMMAND_ACCOUNTING; 233 | collectACRInfo(acr, ss, record_type); 234 | Utils.setMandatory_RFC3588(acr); 235 | return acr; 236 | } 237 | 238 | /** 239 | * Process an ACA 240 | */ 241 | public void handleACA(Message answer) { 242 | //todo: handle acct-realtime-required 243 | 244 | if(answer==null) return; 245 | 246 | try { 247 | Iterator it; 248 | it = answer.iterator(ProtocolConstants.DI_ACCOUNTING_RECORD_NUMBER); 249 | if(!it.hasNext()) { 250 | //Should probably complain about server not following RFC3588 section 9.7.2.. 251 | return; 252 | } 253 | //locate (sub-)session from record number 254 | int record_number = new AVP_Unsigned32(it.next()).queryValue(); 255 | for(Map.Entry e : subsessions.entrySet()) { 256 | if(e.getValue().most_recent_record_number==record_number) { 257 | //clear record number 258 | e.getValue().most_recent_record_number = -1; 259 | return; 260 | } 261 | } 262 | } catch(dk.i1.diameter.InvalidAVPLengthException e) { 263 | //should probably complain here too 264 | } 265 | } 266 | 267 | private void sendACR(Message acr) { 268 | try { 269 | base_session.sessionManager().sendRequest(acr,base_session,null); 270 | } catch(dk.i1.diameter.node.NotARequestException ex) { 271 | //never happens 272 | } catch(dk.i1.diameter.node.NotRoutableException ex) { 273 | base_session.sessionManager().logger.log(Level.INFO,"Could not send ACR for session "+base_session.sessionId()+" :"+ex.toString()); 274 | //peer unavailable? 275 | handleACA(null); 276 | } 277 | } 278 | 279 | /** 280 | * Collect information and put it into an ACR. 281 | * This implementation puts the following AVPs into the ACR: 282 | * session-id, origin-host, origin-realm, destination-realm, acct-record-type 283 | * accounting-record-number, acct-application-id (unless null), 284 | * accounting-sub-session-id (unless it is for the whole session), 285 | * acct-interim-interval (maybe), event-timestamp. 286 | * For interim and stop records the following AVPs are added (if non-null) 287 | * acct-session-time, accounting-input-octets, accounting-output-octets, 288 | * accounting-input-packets, accounting-output-packets 289 | *

290 | * Subclasses probably want to override this to add session-type specific AVPs. 291 | * 292 | * @param acr The ACR message being constructed 293 | * @param ss The sub-session 294 | * @param record_type The record type (start/interim/stop/event) 295 | */ 296 | public void collectACRInfo(Message acr, SubSession ss, int record_type) { 297 | base_session.addCommonStuff(acr); 298 | 299 | acr.add(new AVP_Unsigned32(ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE,record_type)); 300 | 301 | accounting_record_number++; 302 | acr.add(new AVP_Unsigned32(ProtocolConstants.DI_ACCOUNTING_RECORD_NUMBER,accounting_record_number)); 303 | ss.most_recent_record_number = accounting_record_number; 304 | 305 | if(acct_application_id!=null) 306 | acr.add(new AVP_Unsigned32(ProtocolConstants.DI_ACCT_APPLICATION_ID,acct_application_id)); 307 | 308 | if(ss.subsession_id!=0) 309 | acr.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_SUB_SESSION_ID,ss.subsession_id)); 310 | 311 | if(acct_multi_session_id!=null) 312 | acr.add(new AVP_UTF8String(ProtocolConstants.DI_ACCT_MULTI_SESSION_ID,acct_multi_session_id)); 313 | 314 | if(ss.interim_interval!=Long.MAX_VALUE) 315 | acr.add(new AVP_Unsigned32(ProtocolConstants.DI_ACCT_INTERIM_INTERVAL,(int)(ss.interim_interval/1000))); 316 | 317 | acr.add(new AVP_Time(ProtocolConstants.DI_EVENT_TIMESTAMP,(int)(System.currentTimeMillis()/1000))); 318 | 319 | //and now for the actual usage information 320 | if(record_type!=ProtocolConstants.DI_ACCOUNTING_RECORD_TYPE_START_RECORD) { 321 | if(ss.acct_session_time!=null) 322 | acr.add(new AVP_Unsigned32(ProtocolConstants.DI_ACCT_SESSION_TIME,(int)(ss.acct_session_time/1000))); 323 | if(ss.acct_input_octets!=null) 324 | acr.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_INPUT_OCTETS,ss.acct_input_octets)); 325 | if(ss.acct_output_octets!=null) 326 | acr.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_OUTPUT_OCTETS,ss.acct_output_octets)); 327 | if(ss.acct_input_packets!=null) 328 | acr.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_INPUT_PACKETS,ss.acct_input_packets)); 329 | if(ss.acct_output_packets!=null) 330 | acr.add(new AVP_Unsigned64(ProtocolConstants.DI_ACCOUNTING_OUTPUT_PACKETS,ss.acct_output_packets)); 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /dk/i1/diameter/node/TCPNode.java: -------------------------------------------------------------------------------- 1 | package dk.i1.diameter.node; 2 | import java.nio.channels.*; 3 | import java.nio.ByteBuffer; 4 | import java.net.*; 5 | import java.util.logging.Logger; 6 | import java.util.logging.Level; 7 | import java.util.Iterator; 8 | import dk.i1.diameter.Message; 9 | 10 | class TCPNode extends NodeImplementation { 11 | private Thread node_thread; 12 | private Selector selector; 13 | private ServerSocketChannel serverChannel; 14 | private boolean please_stop; 15 | private long shutdown_deadline; 16 | public TCPNode(Node node, NodeSettings settings, Logger logger) { 17 | super(node,settings,logger); 18 | } 19 | 20 | void openIO() throws java.io.IOException { 21 | // create a new Selector for use below 22 | selector = Selector.open(); 23 | if(settings.port()!=0) { 24 | // allocate an unbound server socket channel 25 | serverChannel = ServerSocketChannel.open(); 26 | // Get the associated ServerSocket to bind it with 27 | ServerSocket serverSocket = serverChannel.socket(); 28 | // set the port the server channel will listen to 29 | serverSocket.bind(new InetSocketAddress (settings.port())); 30 | } 31 | } 32 | 33 | void start() { 34 | logger.log(Level.FINEST,"Starting TCP node"); 35 | please_stop = false; 36 | node_thread = new SelectThread(); 37 | node_thread.setDaemon(true); 38 | node_thread.start(); 39 | logger.log(Level.FINEST,"Started TCP node"); 40 | } 41 | 42 | void wakeup() { 43 | logger.log(Level.FINEST,"Waking up selector thread"); 44 | selector.wakeup(); 45 | } 46 | 47 | void initiateStop(long shutdown_deadline) { 48 | logger.log(Level.FINEST,"Initiating stop of TCP node"); 49 | please_stop = true; 50 | this.shutdown_deadline = shutdown_deadline; 51 | logger.log(Level.FINEST,"Initiated stop of TCP node"); 52 | } 53 | 54 | void join() { 55 | logger.log(Level.FINEST,"Joining selector thread"); 56 | try { 57 | node_thread.join(); 58 | } catch(java.lang.InterruptedException ex) {} 59 | node_thread = null; 60 | logger.log(Level.FINEST,"Selector thread joined"); 61 | } 62 | 63 | void closeIO() { 64 | logger.log(Level.FINEST,"Closing server channel, etc."); 65 | if(serverChannel!=null) { 66 | try { 67 | serverChannel.close(); 68 | } catch(java.io.IOException ex) {} 69 | } 70 | serverChannel=null; 71 | try { 72 | selector.close(); 73 | } catch(java.io.IOException ex) {} 74 | selector = null; 75 | logger.log(Level.FINEST,"Closed selector, etc."); 76 | } 77 | 78 | private class SelectThread extends Thread { 79 | public SelectThread() { 80 | super("DiameterNode thread (TCP)"); 81 | } 82 | public void run() { 83 | try { 84 | run_(); 85 | if(serverChannel!=null) 86 | serverChannel.close(); 87 | } catch(java.io.IOException ex) {} 88 | } 89 | private void run_() throws java.io.IOException { 90 | if(serverChannel!=null) { 91 | // set non-blocking mode for the listening socket 92 | serverChannel.configureBlocking(false); 93 | 94 | // register the ServerSocketChannel with the Selector 95 | serverChannel.register(selector, SelectionKey.OP_ACCEPT); 96 | } 97 | 98 | for(;;) { 99 | if(please_stop) { 100 | if(System.currentTimeMillis()>=shutdown_deadline) 101 | break; 102 | if(!anyOpenConnections()) 103 | break; 104 | } 105 | long timeout = calcNextTimeout(); 106 | int n; 107 | //System.out.println("selecting..."); 108 | if(timeout!=-1) { 109 | long now=System.currentTimeMillis(); 110 | if(timeout>now) 111 | n = selector.select(timeout-now); 112 | else 113 | n = selector.selectNow(); 114 | } else 115 | n = selector.select(); 116 | //System.out.println("Woke up from select()"); 117 | 118 | // get an iterator over the set of selected keys 119 | Iterator it = selector.selectedKeys().iterator(); 120 | // look at each key in the selected set 121 | while(it.hasNext()) { 122 | SelectionKey key = (SelectionKey)it.next(); 123 | 124 | if(key.isAcceptable()) { 125 | logger.log(Level.FINE,"Got an inbound connection (key is acceptable)"); 126 | ServerSocketChannel server = (ServerSocketChannel)key.channel(); 127 | SocketChannel channel = server.accept(); 128 | InetSocketAddress address = (InetSocketAddress)channel.socket().getRemoteSocketAddress(); 129 | logger.log(Level.INFO,"Got an inbound connection from " + address.toString()); 130 | if(!please_stop) { 131 | TCPConnection conn = new TCPConnection(TCPNode.this,settings.watchdogInterval(),settings.idleTimeout()); 132 | conn.host_id = address.getAddress().getHostAddress(); 133 | conn.state = Connection.State.connected_in; 134 | conn.channel = channel; 135 | channel.configureBlocking(false); 136 | channel.register(selector, SelectionKey.OP_READ, conn); 137 | 138 | registerInboundConnection(conn); 139 | } else { 140 | //We don't want to add the connection if were are shutting down. 141 | channel.close(); 142 | } 143 | } else if(key.isConnectable()) { 144 | logger.log(Level.FINE,"An outbound connection is ready (key is connectable)"); 145 | SocketChannel channel = (SocketChannel)key.channel(); 146 | TCPConnection conn = (TCPConnection)key.attachment(); 147 | try { 148 | if(channel.finishConnect()) { 149 | logger.log(Level.FINEST,"Connected!"); 150 | conn.state = Connection.State.connected_out; 151 | channel.register(selector, SelectionKey.OP_READ, conn); 152 | initiateCER(conn); 153 | } 154 | } catch(java.io.IOException ex) { 155 | logger.log(Level.WARNING,"Connection to '"+conn.host_id+"' failed", ex); 156 | try { 157 | channel.register(selector, 0); 158 | channel.close(); 159 | } catch(java.io.IOException ex2) {} 160 | unregisterConnection(conn); 161 | } 162 | } else if(key.isReadable()) { 163 | logger.log(Level.FINEST,"Key is readable"); 164 | //System.out.println("key is readable"); 165 | SocketChannel channel = (SocketChannel)key.channel(); 166 | TCPConnection conn = (TCPConnection)key.attachment(); 167 | handleReadable(conn); 168 | if(conn.state!=Connection.State.closed && 169 | conn.hasNetOutput()) 170 | channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE, conn); 171 | } else if(key.isWritable()) { 172 | logger.log(Level.FINEST,"Key is writable"); 173 | SocketChannel channel = (SocketChannel)key.channel(); 174 | TCPConnection conn = (TCPConnection)key.attachment(); 175 | synchronized(getLockObject()) { 176 | handleWritable(conn); 177 | if(conn.state!=Connection.State.closed && 178 | conn.hasNetOutput()) 179 | channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE, conn); 180 | } 181 | } 182 | 183 | // remove key from selected set, it's been handled 184 | it.remove(); 185 | } 186 | 187 | runTimers(); 188 | } 189 | 190 | //Remaining connections are close by Node instance 191 | 192 | //selector is closed in stop() 193 | } 194 | } 195 | 196 | private void handleReadable(TCPConnection conn) { 197 | logger.log(Level.FINEST,"handlereadable()..."); 198 | conn.makeSpaceInNetInBuffer(); 199 | ConnectionBuffers connection_buffers = conn.connection_buffers; 200 | logger.log(Level.FINEST,"pre: conn.in_buffer.position=" + connection_buffers.netInBuffer().position()); 201 | int count; 202 | try { 203 | int loop_count=0; 204 | while((count=conn.channel.read(connection_buffers.netInBuffer()))>0 && loop_count++<3) { 205 | logger.log(Level.FINEST,"readloop: connection_buffers.netInBuffer().position=" + connection_buffers.netInBuffer().position()); 206 | conn.makeSpaceInNetInBuffer(); 207 | } 208 | } catch(java.io.IOException ex) { 209 | logger.log(Level.FINE,"got IOException",ex); 210 | closeConnection(conn); 211 | return; 212 | } 213 | conn.processNetInBuffer(); 214 | processInBuffer(conn); 215 | if(count<0 && conn.state!=Connection.State.closed) { 216 | logger.log(Level.FINE,"count<0"); 217 | closeConnection(conn); 218 | return; 219 | } 220 | } 221 | 222 | private void processInBuffer(TCPConnection conn) { 223 | ByteBuffer app_in_buffer = conn.connection_buffers.appInBuffer(); 224 | logger.log(Level.FINEST,"pre: app_in_buffer.position=" + app_in_buffer.position()); 225 | int raw_bytes=app_in_buffer.position(); 226 | byte[] raw = new byte[raw_bytes]; 227 | app_in_buffer.position(0); 228 | app_in_buffer.get(raw); 229 | app_in_buffer.position(raw_bytes); 230 | int offset=0; 231 | //System.out.println("processInBuffer():looping"); 232 | while(offsetmax) last_tried_port=min; 374 | try { 375 | channel.socket().bind(new InetSocketAddress(last_tried_port)); 376 | } catch(java.net.BindException ex) {} 377 | return; 378 | } 379 | throw new BindException("Could not bind socket in range "+min+"-"+max); 380 | } 381 | } 382 | --------------------------------------------------------------------------------