浏览代码

New Module: ims_ocs - the Online-Charging-Server for Diameter

Carsten Bock 8 年之前
父节点
当前提交
490237855e

+ 1 - 1
Makefile.groups

@@ -155,7 +155,7 @@ mod_list_mono=app_mono
 # - modules related to IMS extensions
 mod_list_ims=cdp cdp_avp ims_dialog ims_auth ims_isc ims_icscf ims_qos \
 			   ims_registrar_pcscf ims_registrar_scscf ims_usrloc_pcscf \
-			   ims_usrloc_scscf ims_charging
+			   ims_usrloc_scscf ims_charging ims_ocs
 
 # - modules depending on osp toolkit library
 mod_list_osp=osp

+ 22 - 0
modules/ims_ocs/Makefile

@@ -0,0 +1,22 @@
+#
+# ims_qos make file
+#
+# 
+
+include ../../Makefile.defs
+auto_gen=
+NAME=ims_ocs.so
+LIBS=
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+SER_LIBS+=$(SERLIBPATH)/ims/kamailio_ims
+
+ifneq ($(OS),darwin)
+	LIBS += -lrt
+	LIBS += -lpthread
+endif
+
+include ../../Makefile.modules

+ 4 - 0
modules/ims_ocs/doc/Makefile

@@ -0,0 +1,4 @@
+docs = ims_ocs.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 286 - 0
modules/ims_ocs/doc/ims_ocs.xml

@@ -0,0 +1,286 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<book id="print" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+        <title>IMS Online Charging Server (OCS) Module</title>
+	<authorgroup>
+	    <author>
+		<firstname>Carsten</firstname>
+		<surname>Bock</surname>
+		<affiliation><orgname>ng-voice GmbH</orgname></affiliation>
+		<address>
+		    <email>[email protected]</email>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2016</year>
+	    <holder>ng-voice GmbH</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+    <chapter>
+	<title>Admin Guide</title>
+    <section id="ims_ocs.overview">
+	<title>Overview</title>
+	<para>
+	    This module provides a simple Online Charging Server Module for
+            working with the ims_charging module. It communicates with the 
+            ims_charging module via the Diameter-Ro Interface.
+	</para>
+	<para>
+            This module is dependent on the CDP (C Diameter Peer) modules for 
+            communicating with a Charging-Server as specified in 3GPP
+            specification TS xx.xxx.
+	</para>
+	<para>
+            Please also refer to RFC 4006 (Diameter Credit-Control Application).
+	</para>
+	<para>
+            The module works will create fake SIP messages and provide them
+            to an event route for further operations. It up to the script writer
+            to do the processing.
+	</para>
+    </section>
+
+  <section id="ims_ocs.dependencies">
+    <title>Dependencies</title>
+
+    <section>
+      <title>Kamailio Modules</title>
+
+      <para>The Following mouldes must be loaded before this module:</para>
+
+      <itemizedlist>
+        <listitem>
+          <para>CDP - C Diameter Peer</para>
+        </listitem>
+        <listitem>
+          <para>CDP_AVP - CDP AVP Applications</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+    <section>
+      <title>External Libraries or Applications</title>
+      <para>No external libraries are required.</para>
+    </section>
+    </section>
+
+  <section id="ims_ocs.functionalities">
+    <title>Faked Messages</title>
+    <section id="ims_ocs.diameter2sip">
+      <title>Translating SIP to Diameter</title>
+      <para>The incoming Charging requests are translated into the following methods:</para>
+      <itemizedlist>
+        <listitem>
+          <para>INVITE - For "Start" Charging Requests</para>
+        </listitem>
+        <listitem>
+          <para>UPDATE - For "Interim" Charging Requests</para>
+        </listitem>
+        <listitem>
+          <para>BYE - For "Stop" Charging Requests</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+    <section id="ims_ocs.sipheaders">
+      <title>SIP-Information in the faked messages</title>
+      <para>The faked messages contain the following information:</para>
+      <itemizedlist>
+        <listitem>
+          <para>Method - See previous section</para>
+        </listitem>
+        <listitem>
+          <para>Request-URI - The dialed number of the call</para>
+        </listitem>
+        <listitem>
+          <para>From-Header - The originator of the session</para>
+        </listitem>
+        <listitem>
+          <para>To-Header - Same as request-URI</para>
+        </listitem>
+        <listitem>
+          <para>Call-ID - The Diameter-Charging-ID (not the SIP-Call-ID)</para>
+        </listitem>
+        <listitem>
+          <para>P-Requested-Units - The requested units for this call</para>
+        </listitem>
+        <listitem>
+          <para>P-Used-Units - The used units for this call (only useful for Interim and Stop Events)</para>
+        </listitem>
+        <listitem>
+          <para>P-Access-Network-Info - The used access network - if available</para>
+        </listitem>
+        <listitem>
+          <para>P-Service-Identifier - The service identifier, if you want to diffentiate different services (e.g. Audio and Video)</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+  </section>
+
+
+	<section>
+	<title>Functions</title>
+	<section id="ims_ocs.f.ccr_result">
+		<title>
+		<function moreinfo="none">ccr_result(resultcode, grantedunits, finalunit)</function>
+		</title>
+		<para>
+		This method sets the response code of the Diameter Request.
+		</para>
+		<para>Meaning of the parameters is as follows:</para>
+		<itemizedlist>
+		<listitem>
+			<para>
+			<emphasis>resultcode</emphasis> - the Diameter Response
+			code for the request. Typical response codes are:	
+			</para>
+			<itemizedlist>
+			<listitem>
+				<para>
+				<quote>2001</quote> - Ok
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+				<quote>5030</quote> - User unknown
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+				<quote>5031</quote> - Rating failed
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+				<quote>4010</quote> - End-User Service denied (e.g. Service blocked)
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+				<quote>5006</quote> - Ressources exceeded (e.g. too many concurrent calls)
+				</para>
+			</listitem>
+			</itemizedlist>
+		</listitem>
+		<listitem>
+			<para>
+			<emphasis>grantedunits</emphasis> - the number of granted units for this particular user
+			</para>
+		</listitem>
+		<listitem>
+			<para>
+			<emphasis>finalunit</emphasis> - indication, that all following requests will be denied (this is the final unit for the session)
+                        </para>
+                </listitem>
+		</itemizedlist>
+		<para>
+		This function can be used from the event route.
+		</para>
+		<example>
+		<title><function>ds_select_dst</function> usage</title>
+		<programlisting format="linespecific">
+...
+ccr_result("2001", "600", "0");
+...
+$var(result) = 2001;
+$var(granted) = $hdr(P-Requested-Units);
+$var(final) = 0;
+ccr_result("$var(result)", "$var(granted)", "$var(final)");
+...
+ccr_result("2001", "$hdr(P-Requested-Units)", "0");
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+
+        <section id="ims_ocs.ex.event_routes">
+        <title>Event routes</title>
+        <section>
+                <title>
+                <function moreinfo="none">ocs:ccr-orig</function>
+                </title>
+                <para>
+			This route is called for Charging Requests with the
+			session-case "originating" - a call from a user to
+			another destination.
+                </para>
+        <programlisting  format="linespecific">
+...
+event_route[ocs:ccr-orig] {
+	xlog("Session-Case: Originating\n");
+	xlog("----------------------------------------\n");
+	if (is_method("INVITE")) {
+		xlog("START - Request\n");
+	} else if (is_method("UPDATE")) {
+		xlog("INTERIM - Request\n");
+	} else if (is_method("BYE")) {
+		xlog("STOP - Request\n");
+	}
+	xlog("----------------------------------------\n");
+	xlog("From:               $fu\n");
+	xlog("To:                 $ru\n");
+	xlog("Call-ID:            $ci\n");
+	xlog("Requested Units:    $hdr(P-Requested-Units)\n");
+	xlog("Used Units:         $hdr(P-Used-Units)\n");
+	xlog("Access Network:     $hdr(P-Access-Network-Info)\n");
+	xlog("Service Identifier: $hdr(P-Service-Identifier)\n");
+
+	ccr_result("2001", "600", "0");
+}
+...
+                </programlisting>
+	</section>
+        <section>
+                <title>
+                <function moreinfo="none">ocs:ccr-term</function>
+                </title>
+                <para>
+			This route is called for Charging Requests with the
+			session-case "terminating" - a call to a user from
+			another destination.
+                </para>
+                <para>
+			You can have an "originating" and a "terminating"
+			request for a single call, e.g. if a user calls another
+			user. Since the Diameter-Session-ID is provided as
+			"Call-ID", it will have a different Call-ID, since it
+			is logical two separate sessions.
+                </para>
+                <para>
+			This route is optional.
+                </para>
+        <programlisting  format="linespecific">
+...
+event_route[ocs:ccr-term] {
+	xlog("Session-Case: Terminating\n");
+	xlog("----------------------------------------\n");
+	if (is_method("INVITE")) {
+		xlog("START - Request\n");
+	} else if (is_method("UPDATE")) {
+		xlog("INTERIM - Request\n");
+	} else if (is_method("BYE")) {
+		xlog("STOP - Request\n");
+	}
+	xlog("----------------------------------------\n");
+	xlog("From:               $fu\n");
+	xlog("To:                 $ru\n");
+	xlog("Call-ID:            $ci\n");
+	xlog("Requested Units:    $hdr(P-Requested-Units)\n");
+	xlog("Used Units:         $hdr(P-Used-Units)\n");
+	xlog("Access Network:     $hdr(P-Access-Network-Info)\n");
+	xlog("Service Identifier: $hdr(P-Service-Identifier)\n");
+
+	ccr_result("2001", "600", "0");
+}
+...
+                </programlisting>
+    </section>
+    </section>
+    </chapter>
+</book>
+

+ 617 - 0
modules/ims_ocs/examples/full/kamailio.cfg

@@ -0,0 +1,617 @@
+#!KAMAILIO
+#
+# This config file implements an Online-Charging-Server
+#     - web: http://www.kamailio.org
+#     - git: http://sip-router.org
+#
+# Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php
+# for an explanation of possible statements, functions and parameters.
+#
+# Direct your questions about this file to: <[email protected]>.
+#
+# For more information about the various parameters, functions and statements
+# try http://sip-router.org/wiki/ .
+#
+
+import_file "ocs.cfg"
+
+#!ifndef GRANT
+#!define GRANT 600
+#!endif
+#!ifndef MULTIPLIER
+#!define MULTIPLIER 10000
+#!endif
+
+##!define WITH_DEBUG
+
+#!ifdef WITH_XMLRPC
+listen=tcp:127.0.0.1:5080
+#!endif
+
+
+####### Defined Values #########
+# *** Value defines - IDs used later in config
+
+# - flags
+#	FLT_ - per transaction (message) flags
+#	FLB_ - per branch flags
+
+
+####### Global Parameters #########
+
+#!ifdef WITH_DEBUG
+debug=4
+log_stderror=yes
+#!else
+debug=0
+log_stderror=no
+#!endif
+
+memdbg=5
+memlog=5
+
+log_stderror=no
+sip_warning=no
+
+rundir="/var/run/kamailio_ocs"
+
+user_agent_header="User-Agent: TelcoSuite OCS"
+server_header="Server: TelcoSuite OCS"
+
+/* comment the next line to enable the auto discovery of local aliases
+   based on reverse DNS on IPs (default on) */
+auto_aliases=no
+
+check_via=no    # (cmd. line: -v)
+dns=no          # (cmd. line: -r)
+rev_dns=no      # (cmd. line: -R)
+
+# Do SRV-Loadbalancing:
+dns_srv_lb=no
+# Always: Also try IPv6:
+dns_try_ipv6=no
+# Always prefer IPv6:
+dns_cache_flags=6
+# DNS-Based failover
+use_dns_failover = on
+# Query NAPTR-Records as well:
+dns_try_naptr=no
+
+#!ifdef WITH_XMLRPC
+tcp_accept_no_cl=yes
+tcp_rd_buf_size=16384
+tcp_children=3
+#!else
+disable_tcp=yes
+#!endif
+
+children=3
+log_name="[Charging]"
+
+system.shutdownmode = 0 desc "System shutdown mode"
+system.service = "Online-Charging-Server" desc "Function of this server"
+
+# ------------------ module loading ----------------------------------
+mpath="/usr/lib64/kamailio/modules_k/:/usr/lib64/kamailio/modules/:/usr/local/lib/kamailio/modules/"
+# (we try both the lib64 and the lib directory)
+
+loadmodule "cdp"
+loadmodule "cdp_avp"
+loadmodule "ims_ocs"
+loadmodule "xlog"
+loadmodule "pv"
+loadmodule "sqlops"
+loadmodule "cfgutils"
+loadmodule "db_mysql"
+loadmodule "textops"
+loadmodule "ctl"
+loadmodule "sl"
+loadmodule "tm"
+loadmodule "kex"
+loadmodule "corex"
+loadmodule "cfg_rpc"
+loadmodule "mi_rpc"
+
+# ----- db_cluster params -----
+
+# ----- ctl params -----
+modparam("ctl", "binrpc", "unix:/var/run/kamailio_ocs/kamailio_ctl")
+
+#!ifdef DB_URL2
+loadmodule "db_cluster"
+# ----- db_cluster params -----
+modparam("db_cluster", "connection", DB_URL)
+modparam("db_cluster", "connection", DB_URL2)
+modparam("db_cluster", "cluster", "cluster1=>con1=2s2s;con2=1s1s")
+#!endif
+
+#!ifdef WITH_DEBUG
+loadmodule "debugger"
+modparam("debugger", "mod_hash_size", 5)
+modparam("debugger", "mod_level_mode", 1)
+modparam("debugger", "mod_level", "ims_ocs=3")
+#!endif
+
+#!ifdef WITH_DMQ
+loadmodule "dmq"
+# ----- dmq params -----
+modparam("dmq", "server_address", DMQ_SERVER_ADDRESS)
+modparam("dmq", "notification_address", DMQ_NOTIFICATION_ADDRESS)
+#!endif
+
+loadmodule "htable"
+# ----- htable params -----
+
+#!ifdef WITH_DMQ
+modparam("htable", "enable_dmq", 1)
+# Per User:
+modparam("htable", "htable", "user=>size=16;autoexpire=14400;dmqreplicate=1")
+# Per Call:
+modparam("htable", "htable", "call=>size=16;autoexpire=14400;dmqreplicate=1")
+# Association: Calls/User
+modparam("htable", "htable", "cid=>size=8;autoexpire=14400;dmqreplicate=1")
+#!else
+modparam("htable", "enable_dmq", 0)
+# Per User:
+modparam("htable", "htable", "user=>size=16;autoexpire=14400")
+# Per Call:
+modparam("htable", "htable", "call=>size=16;autoexpire=14400")
+# Association: Calls/User
+modparam("htable", "htable", "cid=>size=8;autoexpire=14400")
+#!endif
+
+# ----- cdp params -----
+modparam("cdp","config_file","/etc/kamailio_ocs/ocs.xml")
+
+# ----- sqlops params -----
+#!ifdef DB_URL2
+modparam("sqlops","sqlcon", "hss_db=>cluster://cluster1")
+#!else
+modparam("sqlops","sqlcon", DB_URL)
+#!endif
+
+#!ifdef WITH_XMLRPC
+loadmodule "xmlrpc"
+# ----- xmlrpc params -----
+modparam("xmlrpc", "route", "XMLRPC");
+modparam("xmlrpc", "url_match", "^/RPC")
+#!endif
+
+route {
+#!ifdef WITH_DMQ
+	if(is_method("KDMQ")) {
+                dmq_handle_message();
+        }
+#!endif
+	drop();
+	exit;
+}
+
+# XMLRPC routing
+#!ifdef WITH_XMLRPC
+route[XMLRPC] {
+	# allow XMLRPC from localhost
+	if ((method=="POST" || method=="GET") && ((src_ip==127.0.0.1)
+#!ifdef XMLRPC_WHITELIST_1
+ || (src_ip == XMLRPC_WHITELIST_1)
+#!endif
+#!ifdef XMLRPC_WHITELIST_2
+ || (src_ip == XMLRPC_WHITELIST_2)
+#!endif
+#!ifdef XMLRPC_WHITELIST_3
+ || (src_ip == XMLRPC_WHITELIST_3)
+#!endif
+        )) {
+		# close connection only for xmlrpclib user agents (there is a bug in
+		# xmlrpclib: it waits for EOF before interpreting the response).
+		if ($hdr(User-Agent) =~ "xmlrpclib")
+			set_reply_close();
+		set_reply_no_connect();
+		dispatch_rpc();
+		exit;
+	}
+	send_reply("403", "Forbidden");
+	exit;
+}
+#!endif
+
+event_route[ocs:ccr-orig] {
+#!ifdef WITH_DEBUG
+	xlog("ORIG: $rm $fu => $ru\n\n");
+#!endif
+	$var(result) = 5012;
+	$var(granted) = 0;
+	$var(final) = 0;
+	$var(multiplier) = MULTIPLIER;
+	$var(id) = 0;
+	
+	if (is_method("INVITE")) {
+		if ($fU =~ "^\+[1-9][0-9]+$")
+			$var(from) = $(fU{s.substr,1,0});
+		else
+			$var(from) = $fU;
+
+		sql_pvquery("hss_db", "SELECT impi.id AS impi_id, imsu.id, CAST(imsu.credit*$var(multiplier) as UNSIGNED), imsu.rate_plan_id, imsu.payment_method, imsu.max_concurrent_calls FROM impu LEFT JOIN impi_impu ON impu.id = impi_impu.id_impu LEFT JOIN impi ON impi.id = impi_impu.id_impi LEFT JOIN imsu ON impi.id_imsu = imsu.id WHERE ((impu.type = 0 AND impu.identity='sip:$var(from)@$fd') OR (impu.type = 0 AND impu.identity='sip:+$var(from)@$fd') OR (impu.type = 0 AND impu.identity='tel:$var(from)') OR (impu.type = 0 AND impu.identity='tel:+$var(from)') OR (impu.type = 0 AND impu.identity='tel:+$var(from)') OR (impu.type = 3 AND 'sip:$var(from)@$fd' REGEXP impu.identity) OR (impu.type = 3 AND 'tel:$var(from)' REGEXP impu.identity) OR (impu.type = 3 AND 'tel:+$var(from)' REGEXP impu.identity)) limit 1;", "$var(impi_id),$var(id),$var(credit),$var(ratecard),$var(payment),$var(concurrent_calls)");
+		if ($retcode != 1) {
+			# User not found, send DIAMETER_USER_UNKNOWN
+			$var(result) = 5030;
+		} else {
+			if (($var(impi_id) == 0) || ($var(id) == 0)) {
+				# User not found, send DIAMETER_USER_UNKNOWN
+				$var(result) = 5030;
+			} else {
+				# Remove "+"
+				if ($rU =~ "^\+[1-9][0-9]+$")
+					strip(1);
+
+				$var(destination_impu_id) = 0;
+				sql_pvquery("hss_db", "SELECT id FROM impu WHERE ((type = 0 AND identity='$ru') OR (type = 0 AND identity='tel:$rU') OR (type = 3 AND '$ru' REGEXP identity) OR (type = 3 AND 'tel:$rU' REGEXP identity)) limit 1;", "$var(destination_impu_id)");
+				if ($retcode != 1) {
+					$var(destination_impu_id) = 0;
+				}
+
+
+				sht_lock("user=>$var(id)::current_calls");
+				if ($sht(user=>$var(id)::current_calls) != $null)
+					$sht(user=>$var(id)::current_calls) = $(sht(user=>$var(id)::current_calls){s.int});
+				sht_unlock("user=>$var(id)::current_calls");
+
+				sht_lock("user=>$var(id)::reserved_credit");
+				if ($sht(user=>$var(id)::reserved_credit) != $null)
+					$sht(user=>$var(id)::reserved_credit) = $(sht(user=>$var(id)::reserved_credit){s.int});
+				sht_unlock("user=>$var(id)::reserved_credit");
+
+
+#!ifdef WITH_DEBUG
+				xlog("IMPI:             $fu ($var(impi_id))\n");
+				xlog("IMSU-ID:          $var(id)\n");
+				xlog("Credit:           $var(credit) (Reserved: $sht(user=>$var(id)::reserved_credit))\n");
+				xlog("Rate-Card:        $var(ratecard)\n");
+				xlog("Payment:          $var(payment)\n");
+				xlog("Concurrent-Calls: $var(concurrent_calls), currently ($sht(user=>$var(id)::current_calls))\n");
+				if ($var(destination_impu_id) > 0) {
+					xlog(">>>>>>>>>>>>>>>>>>>>>> DESTINATION ($ru) IS ONNET ($var(destination_impu_id))!\n");
+				}
+#!endif
+				if (($sht(user=>$var(id)::current_calls) != $null) && ($var(concurrent_calls) >= 0)) {
+					if ($sht(user=>$var(id)::current_calls) >= $var(concurrent_calls)) {
+						$var(result) = 5006; # DIAMETER_RESOURCES_EXCEEDED
+					} else {
+						$sht(cid=>$ci) = $var(id);
+						$var(result) = 2001;
+					}
+				} else {
+					sht_lock("user=>$var(id)::current_calls");
+					if ($sht(user=>$var(id)::current_calls) == $null) {
+						$sht(user=>$var(id)::current_calls) = 0;
+					}
+					sht_unlock("user=>$var(id)::current_calls");
+					$sht(cid=>$ci) = $var(id);
+					$var(result) = 2001;
+				}
+			}
+			if ($var(result) == "2001") {
+				$var(result) = 5031; # DIAMETER_RATING_FAILED
+				if ($var(destination_impu_id) > 0) {
+					sql_pvquery("hss_db", "SELECT id, description, country_name, iso3166, type, CAST(rate_init*$var(multiplier) as UNSIGNED), rate_init_interval, CAST(rate_follow*$var(multiplier) as UNSIGNED), rate_follow_interval, blocked, max_call_duration FROM call_rates WHERE rate_plan_id = $var(ratecard) AND destination_prefix = 'ONNET' limit 1;", "$var(rate_id),$var(rate_name),$var(country),$var(iso),$var(type),$var(rate_init),$var(rate_init_interval),$var(rate_follow),$var(rate_follow_interval),$var(blocked),$var(maxduration)");
+					if ($retcode == 1) {
+						$var(result) = 2001;
+					}
+				}
+				if ($var(result) == 5031) {
+					if ($rU =~ "^[1-9][0-9]+$") {
+						sql_pvquery("hss_db", "SELECT id, description, country_name, iso3166, type, CAST(rate_init*$var(multiplier) as UNSIGNED), rate_init_interval, CAST(rate_follow*$var(multiplier) as UNSIGNED), rate_follow_interval, blocked, max_call_duration FROM call_rates WHERE rate_plan_id = $var(ratecard) AND destination_prefix = SUBSTRING('$rU', 1, LENGTH(destination_prefix)) ORDER BY LENGTH(destination_prefix) DESC limit 1;", "$var(rate_id),$var(rate_name),$var(country),$var(iso),$var(type),$var(rate_init),$var(rate_init_interval),$var(rate_follow),$var(rate_follow_interval),$var(blocked),$var(maxduration)");
+						if ($retcode == 1) {
+							$var(result) = 2001;
+						} else {
+							$var(result) = 5031; # DIAMETER_RATING_FAILED
+						}
+					}
+				}
+				if ($var(result) == "2001") {
+#!ifdef WITH_DEBUG
+					xlog("Rate-ID:      $var(rate_id)\n"); 
+					xlog("Country:      $var(iso): $var(country) ($var(type))\n"); 
+					xlog("Destination:  $var(rate_name) ($rU)\n"); 
+					xlog("Rate:         $var(rate_init)/$var(rate_init_interval), $var(rate_follow)/$var(rate_follow_interval)\n"); 
+					xlog("Blocked:      $var(blocked)\n");
+					xlog("Max-Duration: $var(maxduration)\n");
+#!endif
+					if ($var(blocked) == 1) {
+						$var(result) = 4010; # DIAMETER_END_USER_SERVICE_DENIED
+					} else {
+						$var(used_credit) = 0;
+						if ($var(payment) == 1) {
+							# Prepaid
+							if ($sht(user=>$var(id)::reserved_credit) != $null)
+								$var(current_credit) = $var(credit) - $sht(user=>$var(id)::reserved_credit);
+							else
+								$var(current_credit) = $var(credit);
+							if ($var(rate_init) > $var(current_credit)) {
+								$var(result) = 4012; # DIAMETER_CREDIT_LIMIT_REACHED
+								$var(granted) = 0;
+								$var(used_credit) = 0;
+							} else {
+								$var(used_credit) = $var(rate_init);
+								$var(granted) = $var(rate_init_interval);
+								if (($var(rate_follow) == 0) || ($var(rate_follow_interval) <= 0)) {
+									$var(granted) = GRANT;
+								} else {
+									$var(used_credit) = $var(rate_init);
+									while ($var(granted) < GRANT) {
+										if ($var(rate_follow) > ($var(current_credit) - $var(used_credit))) {
+											$var(final) = 1;
+											break;
+										}
+										$var(granted) = $var(granted) + $var(rate_follow_interval);
+										$var(used_credit) = $var(used_credit) + $var(rate_follow);
+									}
+									# If it's not sufficient for the next unit, it's the final unit:
+									if ($var(rate_follow) > ($var(current_credit) - $var(used_credit))) {
+										$var(final) = 1;
+									}
+								}
+							}
+						} else {
+							# Postpaid
+							$var(granted) = $var(rate_init_interval);
+							$var(used_credit) = $var(rate_init);
+							if (($var(rate_follow) == 0) || ($var(rate_follow_interval) <= 0)) {
+								$var(granted) = GRANT;
+							} else {
+								while ($var(granted) < GRANT) {
+									$var(granted) = $var(granted) + $var(rate_follow_interval);
+									$var(used_credit) = $var(used_credit) + $var(rate_follow);
+								}
+							}
+						}
+						if ($var(result) == "2001") {
+							$sht(call=>$ci::follow_rate) = $(var(rate_follow){s.int});
+							$sht(call=>$ci::follow_unit) = $(var(rate_follow_interval){s.int});
+							$sht(call=>$ci::initial_rate) = $(var(rate_init){s.int});
+							$sht(call=>$ci::initial_unit) = $(var(rate_init_interval){s.int});
+
+							$sht(call=>$ci::reserved_credit) = $(var(used_credit){s.int});
+							$sht(call=>$ci::used_unit) = 0;
+							$sht(call=>$ci::granted_unit) = $var(granted);
+
+							sht_lock("user=>$var(id)::credit");
+							if ($var(payment) == 1)
+								$sht(user=>$var(id)::credit) = $var(credit);
+							else
+								$sht(user=>$var(id)::credit) = -1;
+							sht_unlock("user=>$var(id)::credit");
+
+							sht_lock("user=>$var(id)::reserved_credit");
+							if ($sht(user=>$var(id)::reserved_credit) == $null) {
+								$sht(user=>$var(id)::reserved_credit) = $(var(used_credit){s.int});						
+							} else {
+								$sht(user=>$var(id)::reserved_credit) = $sht(user=>$var(id)::reserved_credit) + $(var(used_credit){s.int});						
+							}
+							sht_unlock("user=>$var(id)::reserved_credit");
+
+							$sht(call=>$ci::max_units) = $null;
+							if ($var(maxduration) > 0) {
+								$sht(call=>$ci::max_units) = $var(maxduration);
+								if ($var(maxduration) < $var(granted)) {
+									$var(granted) = $var(maxduration);
+									$var(final) = 1;
+								}
+							}
+						}
+#!ifdef WITH_DEBUG
+						xlog("Granted $var(granted)s, reserved new credit $var(used_credit)\n");
+#!endif
+					}
+				} else {
+					$var(result) = 5031; # DIAMETER_RATING_FAILED
+				}
+			}
+		}
+		if ($var(result) == "2001") {
+			sht_lock("user=>$var(id)::current_calls");
+			$sht(user=>$var(id)::current_calls) = $sht(user=>$var(id)::current_calls) + 1;
+			sht_unlock("user=>$var(id)::current_calls");
+#!ifdef WITH_DEBUG
+			xlog("User has now $sht(user=>$var(id)::current_calls) Calls, allowed is $var(concurrent_calls)\n");
+			xlog("INSERT INTO tb_processed_cdrs (imsu_id, impi_id, dest_e164, callid, start, call_rate) VALUES ($var(id), $var(impi_id), '$fU', '$rU', '$ci', now(), $var(id), $var(rate_id))\n");
+#!endif
+			sql_query("hss_db", "INSERT INTO tb_processed_cdrs (imsu_id, impi_id, src_e164, dest_e164, callid, start, call_rate) VALUES ($var(id), $var(impi_id), '$fU', '$rU', '$ci', now(), $var(rate_id));");
+			if ($retcode == -1) {
+				$var(result) = 5012;
+			}
+		}
+	} else if (is_method("UPDATE")) {
+		$var(id) = $sht(cid=>$ci);
+		$var(used_credit) = 0;
+		$var(granted) = 0;
+		$var(credit) = $sht(user=>$var(id)::credit);
+		if ($var(credit) >= 0) {
+			# Prepaid
+			if (($sht(call=>$ci::follow_rate) == 0) || ($sht(call=>$ci::follow_unit) <= 0)) {
+				$var(granted) = GRANT;
+			} else {
+				$var(current_credit) = $sht(user=>$var(id)::credit) - $sht(user=>$var(id)::reserved_credit);
+				while ($var(granted) < GRANT) {
+					if ($sht(call=>$ci::follow_rate) > ($var(current_credit) - $var(used_credit))) {
+						$var(final) = 1;
+						break;
+					}
+					$var(granted) = $var(granted) + $sht(call=>$ci::follow_unit);
+					$var(used_credit) = $var(used_credit) + $sht(call=>$ci::follow_rate);
+				}
+				# If it's not sufficient for the next unit, it's the final unit:
+				if ($sht(call=>$ci::follow_rate) > ($var(current_credit) - $var(used_credit))) {
+					$var(final) = 1;
+				}
+			}
+		} else {
+			# Postpaid
+			if (($sht(call=>$ci::follow_rate) == 0) || ($sht(call=>$ci::follow_unit) <= 0)) {
+				$var(granted) = GRANT;
+			} else {
+				while ($var(granted) < GRANT) {
+					$var(granted) = $var(granted) + $sht(call=>$ci::follow_unit);
+					$var(used_credit) = $var(used_credit) + $sht(call=>$ci::follow_rate);
+				}
+			}
+		}
+		$sht(call=>$ci::used_unit) = $sht(call=>$ci::used_unit) + $(hdr(P-Used-Units){s.int});
+		if ($sht(call=>$ci::max_units) > 0) {
+			$var(cur_granted) = $sht(call=>$ci::used_unit) + $var(granted);
+			$var(max_granted) = $sht(call=>$ci::max_units);
+			if ($var(max_granted) > $var(cur_granted)) {
+				$var(granted) = $sht(call=>$ci::max_units) - $sht(call=>$ci::used_unit);
+				$var(final) = 1;
+			}
+		}
+		if ($var(granted) <= 0) {
+			$var(result) = 4012; # DIAMETER_CREDIT_LIMIT_REACHED
+			$var(granted) = 0;		
+		} else {
+			$var(granted) = $var(granted) + ($sht(call=>$ci::granted_unit) - $(hdr(P-Used-Units){s.int}));
+			$sht(call=>$ci::granted_unit) = $var(granted);
+			$var(result) = 2001;
+		}
+
+		sht_lock("user=>$var(id)::reserved_credit");
+		$sht(user=>$var(id)::reserved_credit) = $sht(user=>$var(id)::reserved_credit) + $var(used_credit);
+		sht_unlock("user=>$var(id)::reserved_credit");
+
+		$sht(call=>$ci::reserved_credit) = $sht(call=>$ci::reserved_credit) + $var(used_credit);
+#!ifdef WITH_DEBUG
+		xlog("$var(result): Granted $var(granted)s (used $sht(call=>$ci::used_unit)), total granted $sht(call=>$ci::granted_unit), reserved credit $sht(user=>$var(id)::reserved_credit), new $var(used_credit)\n");
+#!endif
+	} else if (is_method("BYE")) {
+		$var(id) = $sht(cid=>$ci);
+#!ifdef WITH_DEBUG
+		xlog("$ci: User $var(id) has $sht(user=>$var(id)::current_calls) Calls, decrementing\n");
+#!endif
+		if ($var(id) != $null) {
+			sht_lock("user=>$var(id)::current_calls");
+			if ($sht(user=>$var(id)::current_calls) <= 0) {
+				$sht(user=>$var(id)::current_calls) = $null;
+			} else {
+				$sht(user=>$var(id)::current_calls) = $sht(user=>$var(id)::current_calls) - 1;
+			}
+			sht_unlock("user=>$var(id)::current_calls");
+		}
+		$var(used) = $sht(call=>$ci::used_unit) + $(hdr(P-Used-Units){s.int});
+		$var(credit) = $sht(user=>$var(id)::credit);
+		if ($var(used) > 0) {
+			$var(used_credit) = $sht(call=>$ci::initial_rate);
+			$var(used) = $var(used) - $sht(call=>$ci::initial_unit);
+			if (($sht(call=>$ci::follow_rate) != 0) && ($sht(call=>$ci::follow_unit) != 0) && ($var(used) > 0)) {
+				while ($var(used) > 0) {
+					$var(used) = $var(used) - $sht(call=>$ci::follow_unit);
+					$var(used_credit) = $var(used_credit) + $sht(call=>$ci::follow_rate);
+				}
+			} else {
+				$var(used) = $sht(call=>$ci::used_unit);
+			}
+		} else {
+			$var(used_credit) = 0;
+			$var(used) = 0;
+		}
+		if (($var(used_credit) > 0) && ($var(credit) > 0)) {
+			sht_lock("user=>$var(id)::credit");
+			$sht(user=>$var(id)::credit) = $sht(user=>$var(id)::credit) - $var(used_credit);
+			sht_unlock("user=>$var(id)::credit");
+			sql_query("hss_db", "UPDATE imsu SET credit=credit - ($var(used_credit)/$var(multiplier)) WHERE id=$var(id);");
+			if ($retcode == -1) {
+				$var(result) = 5012;
+			}
+		}
+		$var(duration) = $sht(call=>$ci::used_unit) + $(hdr(P-Used-Units){s.int});
+#!ifdef WITH_DEBUG
+		xlog("Call-Cost: $var(used_credit), Duration $var(duration)\n");
+		xlog("Credit: $sht(user=>$var(id)::credit)\n");
+#!endif
+		sht_lock("user=>$var(id)::reserved_credit");
+		$sht(user=>$var(id)::reserved_credit) = $sht(user=>$var(id)::reserved_credit) - $sht(call=>$ci::reserved_credit);
+		sht_unlock("user=>$var(id)::reserved_credit");
+
+		sql_query("hss_db", "UPDATE tb_processed_cdrs SET connect=DATE_SUB(now(), interval $var(duration) second), stop=now(), duration=$var(duration), price=($var(used_credit)/$var(multiplier)) WHERE callid='$ci';");
+		if ($retcode == -1) {
+			$var(result) = 5012;
+		}
+
+		$var(result) = "2001";
+	}
+
+	ccr_result("$var(result)", "$var(granted)", "$var(final)");
+}
+
+event_route[ocs:ccr-term] {
+	# For the terminating case, we only check the user existence and Line-Limit
+	if ($fU =~ "^\+[1-9][0-9]+$")
+		$var(from) = $(fU{s.substr,1,0});
+	else
+		$var(from) = $fU;
+
+#!ifdef WITH_DEBUG
+	xlog("TERM: $rm $var(from) => $ru\n\n");
+#!endif
+	$var(result) = "5012";
+	$var(granted) = "0";
+	$var(final) = "0";
+
+	if (is_method("INVITE")) {
+		sql_pvquery("hss_db", "SELECT impi.id AS impi_id, imsu.id, imsu.max_concurrent_calls FROM impu LEFT JOIN impi_impu ON impu.id = impi_impu.id_impu LEFT JOIN impi ON impi.id = impi_impu.id_impi LEFT JOIN imsu ON impi.id_imsu = imsu.id WHERE ((impu.type = 0 AND impu.identity='sip:$var(from)@$fd') OR (impu.type = 0 AND impu.identity='sip:+$var(from)@$fd') OR (impu.type = 0 AND impu.identity='tel:$var(from)') OR (impu.type = 0 AND impu.identity='tel:+$var(from)') OR (impu.type = 3 AND 'sip:$var(from)@$fd' REGEXP impu.identity) OR (impu.type = 3 AND 'sip:+$var(from)@$fd' REGEXP impu.identity) OR (impu.type = 3 AND 'tel:$var(from)' REGEXP impu.identity) OR (impu.type = 3 AND 'tel:+$var(from)' REGEXP impu.identity)) limit 1;", "$var(impi_id),$var(id),$var(concurrent_calls)");
+		if ($retcode != 1) {
+			# User not found, send DIAMETER_USER_UNKNOWN
+			$var(result) = 5030;
+		} else {
+#!ifdef WITH_DEBUG
+			xlog("IMPI:             $fu ($var(impi_id))\n");
+			xlog("IMSU-ID:          $var(id)\n");
+			xlog("Concurrent-Calls: $var(concurrent_calls), currently ($sht(user=>$var(id)::current_calls))\n");
+#!endif
+
+			if (($sht(user=>$var(id)::current_calls) != $null) && ($var(concurrent_calls) >= 0)) {
+				if ($sht(user=>$var(id)::current_calls) >= $var(concurrent_calls)) {
+					$var(result) = 5006; # DIAMETER_RESOURCES_EXCEEDED
+				} else {
+					$sht(cid=>$ci) = $var(id);
+					sht_lock("user=>$var(id)::current_calls");
+					$sht(user=>$var(id)::current_calls) = $sht(user=>$var(id)::current_calls) + 1;
+					sht_unlock("user=>$var(id)::current_calls");
+					$var(result) = 2001;
+					$var(granted) = GRANT;
+				}
+			} else {
+				sht_lock("user=>$var(id)::current_calls");
+				if ($sht(user=>$var(id)::current_calls) == $null) {
+					$sht(user=>$var(id)::current_calls) = 1;
+				} else {
+					$sht(user=>$var(id)::current_calls) = $sht(user=>$var(id)::current_calls) + 1;
+				}
+				sht_unlock("user=>$var(id)::current_calls");
+				$sht(cid=>$ci) = $var(id);
+				$var(result) = 2001;
+				$var(granted) = GRANT;
+			}
+		}
+	} else if (is_method("UPDATE")) {
+		$var(result) = 2001;
+		$var(granted) = GRANT;
+	} else if (is_method("BYE")) {
+		$var(id) = $sht(cid=>$ci);
+#!ifdef WITH_DEBUG
+		xlog("$ci: User $var(id) has $sht(user=>$var(id)::current_calls) Calls, decrementing\n");
+#!endif
+		if ($var(id) != $null) {
+			sht_lock("user=>$var(id)::current_calls");
+			if ($sht(user=>$var(id)::current_calls) <= 0) {
+				$sht(user=>$var(id)::current_calls) = $null;
+			} else {
+				$sht(user=>$var(id)::current_calls) = $sht(user=>$var(id)::current_calls) - 1;
+			}
+			sht_unlock("user=>$var(id)::current_calls");
+		}
+		$var(result) = 2001;
+	}
+	// xlog("$rm: $var(result): $var(granted) units...\n");
+
+	ccr_result("$var(result)", "$var(granted)", "$var(final)");
+}

+ 36 - 0
modules/ims_ocs/examples/full/ocs.cfg.sample

@@ -0,0 +1,36 @@
+# IP-Adress for incoming SIP-Traffic, in the following format:
+
+# SIP / UDP
+listen=udp:11.22.33.44:5080
+# SIP / TCP
+#listen=tcp:11.22.33.44:5060
+# SIP / TCP/TLS
+#listen=tls:11.22.33.44:5061
+
+# Connection URL for the database:
+#!define DB_URL "con1=>mysql://ocs:[email protected]/hss_db"
+##!define DB_URL2 "con2=>mysql://ocs:[email protected]/hss_db"
+
+# Allowed IPs for XML-RPC-Queries
+##!define XMLRPC_WHITELIST_1 "127.0.0.1"
+##!define XMLRPC_WHITELIST_2 "127.0.0.1"
+##!define XMLRPC_WHITELIST_3 "127.0.0.1"
+
+##!define DMQ_SERVER_ADDRESS "sip:11.22.33.44:5080"
+##!define DMQ_NOTIFICATION_ADDRESS "sip:11.22.33.44:5080"
+
+#
+# Several features can be enabled using '#!define WITH_FEATURE' directives:
+#
+# *** To run in debug mode: 
+#     - define WITH_DEBUG
+#
+# *** To enable XMLRPC support execute:
+#     - define WITH_XMLRPC
+#     - adjust route[XMLRPC] for access policy
+#
+# Enabled Features for this host:
+##!define WITH_XMLRPC
+##!define WITH_DMQ
+##!define WITH_DEBUG
+

+ 22 - 0
modules/ims_ocs/examples/full/ocs.xml.sample

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DiameterPeer 
+	FQDN="ocs.mnc001.mcc001.3gppnetwork.org"
+	Realm="ims.mnc001.mcc001.3gppnetwork.org"
+	Vendor_Id="10415"
+	Product_Name="CDiameterPeer"
+	AcceptUnknownPeers="1"
+	DropUnknownOnDisconnect="1"
+	Tc="30"
+	Workers="4"
+	QueueLength="8"
+	TransactionTimeout="5"
+	SessionsHashSize="128"
+	DefaultAuthSessionTimeout="3600"
+	MaxAuthSessionTimeout="3600"
+>
+	<Peer FQDN="scscf.mnc001.mcc001.3gppnetwork.org" Realm="ims.mnc001.mcc001.3gppnetwork.org" port="3870"/>
+		
+	<Acceptor port="3871" bind="11.22.33.44"/>
+
+        <Auth id="4" vendor="10415"/> <!--3GPP Ro -->              
+</DiameterPeer>

+ 143 - 0
modules/ims_ocs/examples/simple/kamailio.cfg

@@ -0,0 +1,143 @@
+#!KAMAILIO
+#
+# This config file implements an Online-Charging-Server
+#     - web: http://www.kamailio.org
+#     - git: http://github.com/kamailio/kamailio
+#
+# Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php
+# for an explanation of possible statements, functions and parameters.
+#
+# Direct your questions about this file to: <[email protected]>.
+#
+# For more information about the various parameters, functions and statements
+# try http://sip-router.org/wiki/ .
+#
+
+import_file "ocs.cfg"
+
+####### Defined Values #########
+# *** Value defines - IDs used later in config
+
+# - flags
+#	FLT_ - per transaction (message) flags
+#	FLB_ - per branch flags
+
+
+####### Global Parameters #########
+
+#!ifdef WITH_DEBUG
+debug=4
+log_stderror=yes
+#!else
+debug=0
+log_stderror=no
+#!endif
+
+memdbg=5
+memlog=5
+
+log_stderror=no
+sip_warning=no
+
+rundir="/var/run/kamailio_ocs"
+
+user_agent_header="User-Agent: TelcoSuite OCS"
+server_header="Server: TelcoSuite OCS"
+
+/* comment the next line to enable the auto discovery of local aliases
+   based on reverse DNS on IPs (default on) */
+auto_aliases=no
+
+check_via=no    # (cmd. line: -v)
+dns=no          # (cmd. line: -r)
+rev_dns=no      # (cmd. line: -R)
+
+# Do SRV-Loadbalancing:
+dns_srv_lb=no
+# Always: Also try IPv6:
+dns_try_ipv6=no
+# Always prefer IPv6:
+dns_cache_flags=6
+# DNS-Based failover
+use_dns_failover = on
+# Query NAPTR-Records as well:
+dns_try_naptr=no
+
+children=3
+log_name="[Charging]"
+
+system.shutdownmode = 0 desc "System shutdown mode"
+system.service = "Online-Charging-Server" desc "Function of this server"
+
+# ------------------ module loading ----------------------------------
+mpath="/usr/lib64/kamailio/modules_k/:/usr/lib64/kamailio/modules/:/usr/local/lib/kamailio/modules/"
+# (we try both the lib64 and the lib directory)
+
+loadmodule "cdp"
+loadmodule "cdp_avp"
+loadmodule "ims_ocs"
+loadmodule "xlog"
+loadmodule "pv"
+loadmodule "cfgutils"
+loadmodule "textops"
+loadmodule "ctl"
+loadmodule "sl"
+loadmodule "tm"
+loadmodule "kex"
+loadmodule "corex"
+
+# ----- ctl params -----
+modparam("ctl", "binrpc", "unix:/var/run/kamailio_ocs/kamailio_ctl")
+
+# ----- cdp params -----
+modparam("cdp","config_file","/etc/kamailio_ocs/ocs.xml")
+
+route {
+	# Do nothing, we only do Diameter requests
+	drop();
+	exit;
+}
+
+event_route[ocs:ccr-orig] {
+	xlog("Session-Case: Originating\n");
+	xlog("----------------------------------------\n");
+	if (is_method("INVITE")) {
+		xlog("START - Request\n");
+	} else if (is_method("UPDATE")) {
+		xlog("INTERIM - Request\n");
+	} else if (is_method("BYE")) {
+		xlog("STOP - Request\n");
+	}
+	xlog("----------------------------------------\n");
+	xlog("From:               $fu\n");
+	xlog("To:                 $ru\n");
+	xlog("Call-ID:            $ci\n");
+	xlog("Requested Units:    $hdr(P-Requested-Units)\n");
+	xlog("Used Units:         $hdr(P-Used-Units)\n");
+	xlog("Access Network:     $hdr(P-Access-Network-Info)\n");
+	xlog("Service Identifier: $hdr(P-Service-Identifier)\n");
+
+	ccr_result("2001", "600", "0");
+}
+
+event_route[ocs:ccr-term] {
+	xlog("Session-Case: Terminating\n");
+	xlog("----------------------------------------\n");
+	if (is_method("INVITE")) {
+		xlog("START - Request\n");
+	} else if (is_method("UPDATE")) {
+		xlog("INTERIM - Request\n");
+	} else if (is_method("BYE")) {
+		xlog("STOP - Request\n");
+	}
+	xlog("----------------------------------------\n");
+	xlog("From:               $fu\n");
+	xlog("To:                 $ru\n");
+	xlog("Call-ID:            $ci\n");
+	xlog("Requested Units:    $hdr(P-Requested-Units)\n");
+	xlog("Used Units:         $hdr(P-Used-Units)\n");
+	xlog("Access Network:     $hdr(P-Access-Network-Info)\n");
+	xlog("Service Identifier: $hdr(P-Service-Identifier)\n");
+
+	ccr_result("2001", "600", "0");
+}

+ 9 - 0
modules/ims_ocs/examples/simple/ocs.cfg.sample

@@ -0,0 +1,9 @@
+# IP-Adress for incoming SIP-Traffic, in the following format:
+
+# SIP / UDP
+listen=udp:11.22.33.44:5080
+# SIP / TCP
+#listen=tcp:11.22.33.44:5060
+# SIP / TCP/TLS
+#listen=tls:11.22.33.44:5061
+

+ 22 - 0
modules/ims_ocs/examples/simple/ocs.xml.sample

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DiameterPeer 
+	FQDN="ocs.mnc001.mcc001.3gppnetwork.org"
+	Realm="ims.mnc001.mcc001.3gppnetwork.org"
+	Vendor_Id="10415"
+	Product_Name="CDiameterPeer"
+	AcceptUnknownPeers="1"
+	DropUnknownOnDisconnect="1"
+	Tc="30"
+	Workers="4"
+	QueueLength="8"
+	TransactionTimeout="5"
+	SessionsHashSize="128"
+	DefaultAuthSessionTimeout="3600"
+	MaxAuthSessionTimeout="3600"
+>
+	<Peer FQDN="scscf.mnc001.mcc001.3gppnetwork.org" Realm="ims.mnc001.mcc001.3gppnetwork.org" port="3870"/>
+		
+	<Acceptor port="3871" bind="11.22.33.44"/>
+
+        <Auth id="4" vendor="10415"/> <!--3GPP Ro -->              
+</DiameterPeer>

+ 254 - 0
modules/ims_ocs/mod.c

@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2016 ng-voice GmbH, [email protected]
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#include "../../sr_module.h"
+#include "../../route.h"
+#include "../cdp/cdp_load.h"
+#include "../cdp_avp/mod_export.h"
+#include "../../parser/msg_parser.h"
+#include "mod.h"
+#include "msg_faker.h"
+#include "ocs_avp_helper.h"
+
+MODULE_VERSION
+
+extern gen_lock_t* process_lock; /* lock on the process table */
+
+struct cdp_binds cdpb;
+
+cdp_avp_bind_t *cdp_avp;
+
+/** module functions */
+static int mod_init(void);
+static int mod_child_init(int);
+static void mod_destroy(void);
+
+int * callback_singleton; /*< Callback singleton */
+
+int result_code = 0;
+int granted_units = 0;
+int final_unit = 0;
+
+int event_route_ccr_orig = 0;
+int event_route_ccr_term = 0;
+
+static int w_ccr_result(struct sip_msg *msg, char* result, char* grantedunits, char* final);
+
+static cmd_export_t cmds[] = {
+	{"ccr_result", (cmd_function)w_ccr_result, 3, fixup_var_pve_str_12, 0, REQUEST_ROUTE},
+	{ 0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+    { 0, 0, 0}
+};
+
+
+/** module exports */
+struct module_exports exports = {"ims_ocs", DEFAULT_DLFLAGS, /* dlopen flags */
+    cmds, /* Exported functions */
+    params, 0, /* exported statistics */
+    0, /* exported MI functions */
+    0, /* exported pseudo-variables */
+    0, /* extra processes */
+    mod_init, /* module initialization function */
+    0, mod_destroy, mod_child_init /* per-child init function */};
+
+/**
+ * init module function
+ */
+static int mod_init(void) {
+	LM_DBG("Loading...\n");
+	event_route_ccr_orig = route_get(&event_rt, "ocs:ccr-orig");
+	if (event_route_ccr_orig < 0) {
+		LM_ERR("No ocs:ccr-orig event route found\n");
+		goto error;
+	}
+	LM_DBG("Found Route ocs:ccr-orig: %i\n", event_route_ccr_orig);
+
+	event_route_ccr_term = route_get(&event_rt, "ocs:ccr-term");
+	if (event_route_ccr_term < 0) {
+		LM_INFO("No ocs:ccr-term event route found\n");
+	}
+	LM_DBG("Found Route ocs:ccr-term: %i\n", event_route_ccr_term);
+
+
+	callback_singleton = shm_malloc(sizeof (int));
+	*callback_singleton = 0;
+
+	cdp_avp = 0;
+	/* load the CDP API */
+	if (load_cdp_api(&cdpb) != 0) {
+		LM_ERR("can't load CDP API\n");
+        	goto error;
+	    }
+
+	cdp_avp = load_cdp_avp();
+	if (!cdp_avp) {
+		LM_ERR("can't load CDP_AVP API\n");
+		goto error;
+	}
+
+	return 0;
+error:
+	LM_ERR("Failed to initialise ims_ocs module\n");
+	return -1;
+}
+
+/**
+ * Initializes the module in child.
+ */
+static int mod_child_init(int rank) {
+    LM_DBG("Initialization of module in child [%d] \n", rank);
+
+    /* don't do anything for main process and TCP manager process */
+    if (rank == PROC_MAIN || rank == PROC_TCP_MAIN) {
+        return 0;
+    }
+
+    lock_get(process_lock);
+    if ((*callback_singleton) == 0) {
+        *callback_singleton = 1;
+        cdpb.AAAAddRequestHandler(callback_cdp_request, NULL);
+    }
+    lock_release(process_lock);
+
+    return 0;
+}
+
+
+static void mod_destroy(void) {
+
+}
+
+static int w_ccr_result(struct sip_msg *msg, char* result, char* grantedunits, char* final) {
+	str s_result_code, s_granted_units, s_final_unit;
+	if (get_str_fparam(&s_result_code, msg, (fparam_t*)result) < 0) {
+	    LM_ERR("failed to get Result\n");
+	    return -1;
+	}
+	if (str2sint(&s_result_code, &result_code) != 0) {
+		LM_DBG("Invalid result-code (%.*s)\n", s_result_code.len, s_result_code.s);
+	}
+	LM_DBG("Got result: %i (%.*s)\n", result_code, s_result_code.len, s_result_code.s);
+
+	if (get_str_fparam(&s_granted_units, msg, (fparam_t*)grantedunits) < 0) {
+	    LM_ERR("failed to get Granted Units\n");
+	    return -1;
+	}
+	if (str2sint(&s_granted_units, &granted_units) != 0) {
+		LM_DBG("Invalid Granted Units (%.*s)\n", s_granted_units.len, s_granted_units.s);
+	}
+	LM_DBG("Got Granted Units: %i, %.*s\n", granted_units, s_granted_units.len, s_granted_units.s);
+
+	if (get_str_fparam(&s_final_unit, msg, (fparam_t*)final) < 0) {
+	    LM_ERR("failed to get Final Unit\n");
+	    return -1;
+	}
+	if (str2sint(&s_final_unit, &final_unit) != 0) {
+		LM_DBG("Invalid Granted Units (%.*s)\n", s_final_unit.len, s_final_unit.s);
+	}
+	LM_DBG("Got Final Unit: %i, %.*s\n", final_unit, s_final_unit.len, s_final_unit.s);
+	return 1;
+}
+
+AAAMessage* process_ccr(AAAMessage *ccr) {
+	int backup_rt;
+	struct run_act_ctx ctx;
+	struct sip_msg *msg;
+
+	// Initialize values:
+	result_code = 0;
+	granted_units = 0;
+
+	LM_DBG("Processing CCR");
+
+	if ((isOrig(ccr) != 0) && (event_route_ccr_term < 0)) {
+		result_code = DIAMETER_SUCCESS;
+		granted_units = 3600;
+		final_unit = 0;
+	} else {
+		if (faked_aaa_msg(ccr, &msg) != 0) {
+			LM_ERR("Failed to build Fake-Message\n");
+		}
+
+		backup_rt = get_route_type();
+		set_route_type(REQUEST_ROUTE);
+		init_run_actions_ctx(&ctx);
+		if (isOrig(ccr) != 0) {
+			run_top_route(event_rt.rlist[event_route_ccr_term], msg, 0);
+		} else {
+			run_top_route(event_rt.rlist[event_route_ccr_orig], msg, 0);
+		}
+	
+		set_route_type(backup_rt);
+
+		free_sip_msg(msg);
+	}
+
+	LM_DBG("Result-Code is %i, Granted Units %i (Final: %i)\n", result_code, granted_units, final_unit);
+
+	if (result_code == 0) {
+		LM_ERR("event_route did not set Result-Code, aborting\n");
+		result_code = DIAMETER_UNABLE_TO_COMPLY;
+		granted_units = 0;
+		final_unit = 0;
+	}
+
+	AAAMessage *cca;
+	cca = cdpb.AAACreateResponse(ccr);
+	if (!cca) return 0;
+
+	ocs_build_answer(ccr, cca, result_code, granted_units, final_unit);
+
+	return cca;	
+}
+
+/**
+ * Handler for incoming Diameter requests.
+ * @param request - the received request
+ * @param param - generic pointer
+ * @returns the answer to this request
+ */
+AAAMessage* callback_cdp_request(AAAMessage *request, void *param) {
+    if (is_req(request)) {
+
+        switch (request->applicationId) {
+            case IMS_Ro:
+                switch (request->commandCode) {
+		    case IMS_CCR:
+			return process_ccr(request);
+			break;
+                    default:
+                        LM_ERR("Ro request handler(): - Received unknown request for Ro command %d, flags %#1x endtoend %u hopbyhop %u\n", request->commandCode, request->flags, request->endtoendId, request->hopbyhopId);
+                        return 0;
+                        break;
+                }
+                break;
+            default:
+                LM_ERR("Ro request handler(): - Received unknown request for app %d command %d\n", request->applicationId, request->commandCode);
+                return 0;
+                break;
+        }
+    }
+    return 0;
+}
+

+ 37 - 0
modules/ims_ocs/mod.h

@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (C) 2015 ng-voice GmbH, Carsten Bock, [email protected]
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+
+#ifndef IMS_OCS_MOD_H
+#define	IMS_OCS_MOD_H
+
+/** callback functions */
+
+struct cdp_binds cdpb;
+cdp_avp_bind_t *cdp_avp;
+
+struct AAAMessage;
+
+AAAMessage* callback_cdp_request(AAAMessage *request, void *param);
+
+#endif	/* IMS_OCS_MOD_H */
+

+ 145 - 0
modules/ims_ocs/msg_faker.c

@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015 ng-voice GmbH, [email protected]
+ * File is based on cnxcc: msg_faker, written by Carlos Ruiz Díaz (caruizdiaz.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#include "../../parser/msg_parser.h"
+#include "../../globals.h"
+#include "../cdp/cdp_load.h"
+#include "../cdp_avp/mod_export.h"
+#include "ocs_avp_helper.h"
+
+#include "msg_faker.h"
+
+#include <sys/socket.h>
+
+#define FAKED_SIP_SESSION_FORMAT "%.*s %.*s SIP/2.0\r\nVia: SIP/2.0/UDP 127.0.0.1\r\nFrom: %.*s%.*s\r\nTo: %.*s;tag=xyz\r\nCall-ID: %.*s\r\nCSeq: 1 %.*s\r\nContent-Length: 0\r\nP-Requested-Units: %i\r\nP-Used-Units: %i\r\nP-Access-Network-Info: %.*s\r\nP-Service-Identifier: %i\r\n\r\n"
+
+#define FAKED_SIP_SESSION_BUF_LEN	1024
+char _faked_sip_session_buf[FAKED_SIP_SESSION_BUF_LEN];
+
+str CC_INVITE = {"INVITE", 6};
+str CC_UPDATE = {"UPDATE", 6};
+str CC_BYE = {"BYE", 3};
+
+#define RO_CC_START 	1
+#define RO_CC_INTERIM 	2
+#define RO_CC_STOP 	3
+
+static struct sip_msg _faked_msg;
+
+int getMethod(AAAMessage *msg, str ** method) {
+	str s;
+	s = get_avp(msg, AVP_IMS_CCR_Type, 0, __FUNCTION__);
+	if (!s.s) return -1;
+	switch (get_4bytes(s.s)) {
+		case RO_CC_START:
+			*method = &CC_INVITE;
+			break;
+		case RO_CC_INTERIM:
+			*method = &CC_UPDATE;
+			break;
+		case RO_CC_STOP:
+			*method = &CC_BYE;
+			break;
+		default:
+			LM_ERR("Invalid CCR-Type\n");
+			return -1;
+			break;
+	}
+	
+	return 1;
+	
+}
+
+int faked_aaa_msg(AAAMessage *ccr, struct sip_msg **msg) {
+	int type, size;
+	str * method;
+	str prefix = {0, 0};
+	str from_uri = getSubscriptionId1(ccr, &type);
+	str to_uri = getCalledParty(ccr);
+	str callid = getSession(ccr);
+	str access_network_info = getAccessNetwork(ccr);
+	int used_units = 0;
+	int service = 0;
+	int group = 0;
+	int requested_units = getUnits(ccr, &used_units, &service, &group);
+
+	if (getMethod(ccr, &method) < 0) {
+		LM_ERR("Failed to get CCR-Type\n");
+		return -1;
+	}
+
+	if (type != AVP_Subscription_Id_Type_SIP_URI) {
+		prefix.s = "tel:";
+		prefix.len = 4;
+	}
+	
+
+	memset(_faked_sip_session_buf, 0, FAKED_SIP_SESSION_BUF_LEN);
+	memset(&_faked_msg, 0, sizeof(struct sip_msg));
+
+	size = snprintf(_faked_sip_session_buf, FAKED_SIP_SESSION_BUF_LEN, FAKED_SIP_SESSION_FORMAT,
+		/* First-Line METHOD sip:.... */
+		method->len, method->s, to_uri.len, to_uri.s,
+		/* Prefix */
+		prefix.len, prefix.s,
+		/* From-Header */
+		from_uri.len, from_uri.s,
+		/* To-Header */
+		to_uri.len, to_uri.s,
+		/* Call-ID */
+		callid.len, callid.s,
+		/* CSeq (Method) */
+		method->len, method->s,
+		/* Requested / Used Units */
+		requested_units, used_units,
+		/* P-Access-Network-Info */
+		access_network_info.len, access_network_info.s,
+                /* P-Access-Network-Info */
+		service
+	);
+ 
+	LM_DBG("fake msg:\n%s\n", _faked_sip_session_buf);
+
+	_faked_msg.buf = _faked_sip_session_buf;
+	_faked_msg.len = size;
+
+	_faked_msg.set_global_address	= default_global_address;
+	_faked_msg.set_global_port	= default_global_port;
+
+	if (parse_msg(_faked_msg.buf, _faked_msg.len, &_faked_msg) != 0) {
+		LM_ERR("parse_msg failed\n");
+		return -1;
+	}
+
+	_faked_msg.rcv.proto = PROTO_UDP;
+	_faked_msg.rcv.src_port = 5060;
+	_faked_msg.rcv.src_ip.u.addr32[0] = 0x7f000001;
+	_faked_msg.rcv.src_ip.af = AF_INET;
+	_faked_msg.rcv.src_ip.len = 4;
+	_faked_msg.rcv.dst_port = 5060;
+	_faked_msg.rcv.dst_ip.u.addr32[0] = 0x7f000001;
+	_faked_msg.rcv.dst_ip.af = AF_INET;
+	_faked_msg.rcv.dst_ip.len = 4;
+
+	*msg	= &_faked_msg;
+	return 0;
+}

+ 27 - 0
modules/ims_ocs/msg_faker.h

@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 ng-voice GmbH, [email protected]
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#ifndef OCS_MSG_FAKER_H_
+#define OCS_MSG_FAKER_H_
+
+int faked_aaa_msg(AAAMessage *ccr, struct sip_msg **msg);
+
+#endif /* OCS_MSG_FAKER_H_ */

+ 348 - 0
modules/ims_ocs/ocs_avp_helper.c

@@ -0,0 +1,348 @@
+/*
+ *
+ * Copyright (C) 2015 ng-voice GmbH, Carsten Bock, [email protected]
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#include "../cdp/cdp_load.h"
+#include "../cdp_avp/mod_export.h"
+#include "ocs_avp_helper.h"
+#include "mod.h"
+
+/**
+ * Returns the value of a certain AVP from a Diameter message.
+ * @param m - Diameter message to look into
+ * @param avp_code - the code to search for
+ * @param vendorid - the value of the vendor id to look for or 0 if none
+ * @param func - the name of the calling function, for debugging purposes
+ * @returns the str with the payload on success or an empty string on failure
+ */
+str get_avp(AAAMessage *msg,int avp_code,int vendor_id, const char *func) {
+	AAA_AVP *avp;
+	str r={0,0};
+	avp = cdpb.AAAFindMatchingAVP(msg,0,avp_code,vendor_id,0);
+	if (avp==0) {
+		LM_INFO("%s: Failed finding avp\n",func);
+		return r;
+	} else return avp->data;
+}
+
+str getSession(AAAMessage *msg) {
+	AAA_AVP *avp;
+	str r={0,0};
+	avp = cdpb.AAAFindMatchingAVP(msg,0,AVP_Session_Id,0,0);
+	if (avp==0) {
+		LM_INFO("Failed finding avp\n");
+		return r;
+	} else return avp->data;
+}
+
+int getRecordNummber(AAAMessage *msg) {
+	AAA_AVP *avp;
+	avp = cdpb.AAAFindMatchingAVP(msg,0,AVP_Accounting_Record_Number,0,0);
+	if (avp==0) {
+		LM_DBG("Failed finding avp\n");
+		return 0;
+	} else return get_4bytes(avp->data.s);
+}
+
+str getSubscriptionId1(AAAMessage *msg, int * type) {
+	AAA_AVP *avp, *avp_type, *avp_value;
+	str r={0,0};
+	avp = cdpb.AAAFindMatchingAVP(msg,0,AVP_Subscription_Id,0,0);
+	AAA_AVP_LIST list;
+	list = cdp_avp->cdp->AAAUngroupAVPS(avp->data);
+	avp_type = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Subscription_Id_Type, 0, 0);
+	avp_value = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Subscription_Id_Data, 0, 0);
+	
+	if (avp_type) {
+		*type = get_4bytes(avp_type->data.s);
+	} else {
+		LM_DBG("Failed finding type\n");
+		*type = 0;
+	}
+	if (avp_value==0) {
+		LM_DBG("Failed finding value\n");
+	} else {
+		r = avp_value->data;
+	}
+	cdpb.AAAFreeAVPList(&list);
+	return r;
+}
+
+int isOrig(AAAMessage *msg) {
+	AAA_AVP *service, *imsinfo, *role;
+	AAA_AVP_LIST list, list2;
+
+	int result = 0;
+	service = cdpb.AAAFindMatchingAVP(msg,0,AVP_IMS_Service_Information,IMS_vendor_id_3GPP,0);
+	if (service) {
+		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
+		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
+		if (imsinfo) {
+			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
+			role = cdpb.AAAFindMatchingAVPList(list2, list2.head, AVP_IMS_Role_Of_Node, IMS_vendor_id_3GPP, 0);
+			if (role) {
+				result = get_4bytes(role->data.s);
+			}
+			cdpb.AAAFreeAVPList(&list2);
+		} else {
+			LM_DBG("Failed finding IMS-Info\n");
+		}
+		cdpb.AAAFreeAVPList(&list);
+	} else {
+		LM_DBG("Failed finding Service-Info\n");
+	}
+
+	return result;
+}
+
+str getCalledParty(AAAMessage *msg) {
+	AAA_AVP *service, *imsinfo, *calledparty;
+	str r={0,0};
+	service = cdpb.AAAFindMatchingAVP(msg,0,AVP_IMS_Service_Information,IMS_vendor_id_3GPP,0);
+	if (service) {
+		AAA_AVP_LIST list, list2;
+		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
+		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
+		if (imsinfo) {
+			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
+			calledparty = cdpb.AAAFindMatchingAVPList(list2, list2.head, AVP_IMS_Called_Party_Address, IMS_vendor_id_3GPP, 0);
+			if (calledparty) {
+				r = calledparty->data;
+			} else {
+				LM_DBG("Failed finding value\n");
+			}
+			cdpb.AAAFreeAVPList(&list2);
+		} else {
+			LM_DBG("Failed finding IMS-Info\n");
+		}
+		cdpb.AAAFreeAVPList(&list);
+	} else {
+		LM_DBG("Failed finding Service-Info\n");
+	}
+
+	return r;
+}
+
+str getAccessNetwork(AAAMessage *msg) {
+	AAA_AVP *service, *imsinfo, *access;
+	str r={0,0};
+	service = cdpb.AAAFindMatchingAVP(msg,0,AVP_IMS_Service_Information,IMS_vendor_id_3GPP,0);
+	if (service) {
+		AAA_AVP_LIST list, list2;
+		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
+		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
+		if (imsinfo) {
+			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
+			access = cdpb.AAAFindMatchingAVPList(list2, list2.head, AVP_IMS_Access_Network_Information, IMS_vendor_id_3GPP, 0);
+			if (access) {
+				r = access->data;
+			} else {
+				LM_DBG("Failed finding value\n");
+			}
+			cdpb.AAAFreeAVPList(&list2);
+		} else {
+			LM_DBG("Failed finding IMS-Info\n");
+		}
+		cdpb.AAAFreeAVPList(&list);
+	} else {
+		LM_DBG("Failed finding Service-Info\n");
+	}
+	return r;
+}
+
+int getUnits(AAAMessage *msg, int * used, int * service, int * group) {
+	AAA_AVP *avp, *req_units, *value, *used_units, *service_avp, *rating_group;
+	int units = 0;
+	*used = 0;
+	*service = 0;
+	avp = cdpb.AAAFindMatchingAVP(msg,0,AVP_Multiple_Services_Credit_Control,0,0);
+	if (avp) {
+		AAA_AVP_LIST list, list2;
+		list = cdp_avp->cdp->AAAUngroupAVPS(avp->data);
+		req_units = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Requested_Service_Unit, 0, 0);
+		if (req_units) {
+			list2 = cdp_avp->cdp->AAAUngroupAVPS(req_units->data);
+			value = cdpb.AAAFindMatchingAVPList(list2, list2.head, AVP_CC_Time, 0, 0);
+			cdpb.AAAFreeAVPList(&list2);
+			if (value)
+				units = get_4bytes(value->data.s);
+			cdpb.AAAFreeAVPList(&list2);
+		}
+		service_avp = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Service_Identifier, 0, 0);
+		if (service_avp) {
+			*service = get_4bytes(service_avp->data.s);
+		}
+		used_units = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Used_Service_Unit, 0, 0);
+		if (used_units) {
+			list2 = cdp_avp->cdp->AAAUngroupAVPS(used_units->data);
+			value = cdpb.AAAFindMatchingAVPList(list2, list2.head, AVP_CC_Time, 0, 0);
+			if (value)
+				*used = get_4bytes(value->data.s);
+			cdpb.AAAFreeAVPList(&list2);
+		}
+		rating_group = cdpb.AAAFindMatchingAVPList(list, list.head, AVP_Rating_Group, 0, 0);
+		if (rating_group) {
+			*group = get_4bytes(rating_group->data.s);
+		}
+		cdpb.AAAFreeAVPList(&list);
+	}
+	if (*service == 0) LM_WARN("Failed to get service-identifier\n");
+	return units;
+}
+
+
+/**
+ * Create and add an AVP to a Diameter message.
+ * @param m - Diameter message to add to
+ * @param d - the payload data
+ * @param len - length of the payload data
+ * @param avp_code - the code of the AVP
+ * @param flags - flags for the AVP
+ * @param vendorid - the value of the vendor id or 0 if none
+ * @param data_do - what to do with the data when done
+ * @param func - the name of the calling function, for debugging purposes
+ * @returns 1 on success or 0 on failure
+ */
+int ocs_add_avp(AAAMessage *m, char *d, int len, int avp_code, int flags, int vendorid, int data_do, const char *func) {
+    AAA_AVP *avp;
+    if (vendorid != 0) flags |= AAA_AVP_FLAG_VENDOR_SPECIFIC;
+    avp = cdpb.AAACreateAVP(avp_code, flags, vendorid, d, len, data_do);
+    if (!avp) {
+        LM_ERR("%s: Failed creating avp\n", func);
+        return 0;
+    }
+    if (cdpb.AAAAddAVPToMessage(m, avp, m->avpList.tail) != AAA_ERR_SUCCESS) {
+        LM_ERR("%s: Failed adding avp to message\n", func);
+       cdpb.AAAFreeAVP(&avp);
+        return 0;
+    }
+    return 1;
+}
+
+
+/**
+ * Create and add an AVP to a list of AVPs.
+ * @param list - the AVP list to add to
+ * @param d - the payload data
+ * @param len - length of the payload data
+ * @param avp_code - the code of the AVP
+ * @param flags - flags for the AVP
+ * @param vendorid - the value of the vendor id or 0 if none
+ * @param data_do - what to do with the data when done
+ * @param func - the name of the calling function, for debugging purposes
+ * @returns 1 on success or 0 on failure
+ */
+int ocs_add_avp_list(AAA_AVP_LIST *list, char *d, int len, int avp_code,
+	int flags, int vendorid, int data_do, const char *func) {
+    AAA_AVP *avp;
+    if (vendorid != 0) flags |= AAA_AVP_FLAG_VENDOR_SPECIFIC;
+    avp = cdpb.AAACreateAVP(avp_code, flags, vendorid, d, len, data_do);
+    if (!avp) {
+	LM_ERR("%s: Failed creating avp\n", func);
+	return 0;
+    }
+    if (list->tail) {
+	avp->prev = list->tail;
+	avp->next = 0;
+	list->tail->next = avp;
+	list->tail = avp;
+    } else {
+	list->head = avp;
+	list->tail = avp;
+	avp->next = 0;
+	avp->prev = 0;
+    }
+
+    return 1;
+}
+
+int ocs_build_answer(AAAMessage *ccr, AAAMessage *cca, int result_code, int granted_units, int final_unit) {
+	AAA_AVP *avp;
+	AAA_AVP_LIST granted_list, mscc_list, final_list;
+	char x[4];
+	str granted_group, mscc_group, final_group;
+	int service, group, used;
+	
+	if (!ccr) return 0;
+   	if (!cca) return 0;
+
+	// Set some basic data: Application-ID, CCR-Type, CCR-Request-Number
+	set_4bytes(x, IMS_Ro);
+	ocs_add_avp(cca, x, 4, AVP_Acct_Application_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+	
+	avp = cdpb.AAAFindMatchingAVP(ccr,0,AVP_IMS_CCR_Type,0,0);
+	ocs_add_avp(cca, avp->data.s, avp->data.len, AVP_IMS_CCR_Type, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+	avp = cdpb.AAAFindMatchingAVP(ccr,0,AVP_CC_Request_Number,0,0);
+	ocs_add_avp(cca, avp->data.s, avp->data.len,AVP_CC_Request_Number, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+	
+	// Result-Code:
+	set_4bytes(x, result_code);
+	ocs_add_avp(cca, x, 4, AVP_Result_Code, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+	if (result_code == DIAMETER_SUCCESS) {
+		granted_list.head = 0;
+		granted_list.tail = 0;
+		final_list.head = 0;
+		final_list.tail = 0;
+		mscc_list.head = 0;
+		mscc_list.tail = 0;
+
+		getUnits(ccr, &used, &service, &group);
+	
+		set_4bytes(x, group);
+		ocs_add_avp_list(&mscc_list, x, 4, AVP_Rating_Group, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+		set_4bytes(x, service);
+		ocs_add_avp_list(&mscc_list, x, 4, AVP_Service_Identifier, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+		if (granted_units > 0) {
+			set_4bytes(x, granted_units);
+			ocs_add_avp_list(&granted_list, x, 4, AVP_CC_Time, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+			granted_group = cdpb.AAAGroupAVPS(granted_list);
+			cdpb.AAAFreeAVPList(&granted_list);
+			ocs_add_avp_list(&mscc_list, granted_group.s, granted_group.len, AVP_Granted_Service_Unit, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+		}
+
+		// Result-Code:
+		set_4bytes(x, result_code);
+		ocs_add_avp_list(&mscc_list, x, 4, AVP_Result_Code, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+		set_4bytes(x, 86400);
+		ocs_add_avp_list(&mscc_list, x, 4, AVP_Validity_Time, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+
+		if (final_unit > 0) {
+			set_4bytes(x, 0);
+			ocs_add_avp_list(&final_list, x, 4, AVP_Final_Unit_Action, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+			final_group = cdpb.AAAGroupAVPS(final_list);
+			cdpb.AAAFreeAVPList(&final_list);
+			ocs_add_avp_list(&mscc_list, final_group.s, final_group.len, AVP_Final_Unit_Indication, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+		}
+
+
+		mscc_group = cdpb.AAAGroupAVPS(mscc_list);
+		cdpb.AAAFreeAVPList(&mscc_list);
+
+		return ocs_add_avp(cca, mscc_group.s, mscc_group.len, AVP_Multiple_Services_Credit_Control, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+	}
+	return 1;
+}

+ 39 - 0
modules/ims_ocs/ocs_avp_helper.h

@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (C) 2015 ng-voice GmbH, Carsten Bock, [email protected]
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#ifndef OCS_AVP_HELPER_H
+#define OCS_AVP_HELPER_H
+
+#include "../cdp/diameter_api.h"
+
+str get_avp(AAAMessage *msg,int avp_code,int vendor_id, const char *func);
+str getSession(AAAMessage *msg);
+int getRecordNummber(AAAMessage *msg);
+str getSubscriptionId1(AAAMessage *msg, int * type);
+int isOrig(AAAMessage *msg);
+str getCalledParty(AAAMessage *msg);
+int getUnits(AAAMessage *msg, int * used, int * service, int * group);
+str getAccessNetwork(AAAMessage *msg);
+
+int ocs_build_answer(AAAMessage *ccr, AAAMessage *cca, int result_code, int granted_units, int final_unit);
+
+#endif /* OCS_AVP_HELPER_H */

+ 90 - 0
modules/ims_ocs/sem.h

@@ -0,0 +1,90 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Smile Communications, [email protected]
+ * Copyright (C) 2012 Smile Communications, [email protected]
+ * 
+ * The initial version of this code was written by Dragos Vingarzan
+ * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
+ * Fruanhofer Institute. It was and still is maintained in a separate
+ * branch of the original SER. We are therefore migrating it to
+ * Kamailio/SR and look forward to maintaining it from here on out.
+ * 2011/2012 Smile Communications, Pty. Ltd.
+ * ported/maintained/improved by 
+ * Jason Penton (jason(dot)penton(at)smilecoms.com and
+ * Richard Good (richard(dot)good(at)smilecoms.com) as part of an 
+ * effort to add full IMS support to Kamailio/SR using a new and
+ * improved architecture
+ * 
+ * NB: Alot of this code was originally part of OpenIMSCore,
+ * FhG Fokus. 
+ * Copyright (C) 2004-2006 FhG Fokus
+ * Thanks for great work! This is an effort to 
+ * break apart the various CSCF functions into logically separate
+ * components. We hope this will drive wider use. We also feel
+ * that in this way the architecture is more complete and thereby easier
+ * to manage in the Kamailio/SR environment
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ *
+ *
+ * History:
+ * --------
+ *  2011-02-02  initial version (jason.penton)
+ */
+
+#ifndef __SEM_H
+#define __SEM_H
+
+	#include <semaphore.h>
+
+	typedef sem_t gen_sem_t;
+
+	/**
+	 * Create a new unnamed semaphore and initialize it
+	 * @param value - 0 if it should be pre-locked, 1 if not, or how many locks until block
+	 * @return
+	 */
+    #define sem_new(sem_ptr,value)\
+	do {\
+		sem_ptr=shm_malloc(sizeof(gen_sem_t));\
+		if (!sem_ptr){\
+			LM_ERR("Error allocating %lx bytes of shm!\n",sizeof(gen_sem_t));\
+		}	\
+		if (sem_init(sem_ptr, 1, value)<0) {\
+			LM_ERR("Error > %s\n",strerror(errno));\
+		}\
+	} while(0)
+	
+    #define sem_free(sem)\
+	do {\
+		if (sem) {\
+			sem_destroy(sem);\
+			shm_free(sem);\
+			sem=0;\
+		}\
+	} while(0)
+	
+	
+	#define sem_get(sem) sem_wait(sem)
+	#define sem_tryget(sem) sem_trywait(sem)
+	#define sem_timedget(sem,abs_timeout) sem_trywait(sem,abs_timeout)
+	
+	#define sem_release(sem) sem_post(sem)
+
+#endif