Pārlūkot izejas kodu

ims_charging: New module for Diameter-Ro-Operations (IMS-Charging), Merge into Master

Credits go to:
- Jason Penton ([email protected])
- Carlos Ruiz Diaz ([email protected])

Merge branch 'ims_charging'
Carsten Bock 12 gadi atpakaļ
vecāks
revīzija
576dce3cfd

+ 1 - 1
Makefile.groups

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

+ 22 - 0
modules/ims_charging/Makefile

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

+ 675 - 0
modules/ims_charging/README

@@ -0,0 +1,675 @@
+The IMS Charging Module
+
+Jason Penton
+
+   Smile Communications
+   <[email protected]>
+
+Carsten Bock
+
+   ng-voice GmbH
+   <[email protected]>
+
+Carlos Ruiz Diaz
+
+   ng-voice GmbH
+   <[email protected]>
+
+   Copyright © 2013 Smile Communications
+
+   Copyright © 2013 ng-voice GmbH
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Understanding Charging in the IP-Multimedia-Subsystem (IMS)
+
+              3.1. Offline Charging (Rf)
+              3.2. Online Charging (Ro)
+              3.3. Online Charging (Ro): A practical example
+
+        4. Parameters
+
+              4.1. hash_size(int)
+              4.2. interim_update_credits(int)
+              4.3. timer_buffer(int)
+              4.4. ro_forced_peer(string)
+              4.5. ro_auth_expiry(integer)
+              4.6. ro_auth_expiry(integer)
+              4.7. cdp_event_latency(integer)
+              4.8. cdp_event_threshold(integer)
+              4.9. cdp_event_latency_log(integer)
+              4.10. origin_host(string)
+              4.11. origin_realm(string)
+              4.12. destination_host(string)
+              4.13. destination_realm(string)
+              4.14. service_context_id_root(string)
+              4.15. service_context_id_ext(string)
+              4.16. service_context_id_mnc(string)
+              4.17. service_context_id_mcc(string)
+              4.18. service_context_id_release(string)
+
+        5. Functions
+
+              5.1. Ro_CCR(route_name, direction, charge_type, unit_type,
+                      reservation_units)
+
+        6. Statistics
+
+              6.1. Initial CCRs (initial_ccrs)
+              6.2. Interim CCRs (interim_ccrs)
+              6.3. Final CCRs (final_ccrs)
+              6.4. Sucessful initial CCRs (successful_initial_ccrs)
+              6.5. Sucessful interim CCRs (successful_interim_ccrs)
+              6.6. Sucessful final CCRs (successful_final_ccrs)
+              6.7. Failed initial CCRs (failed_initial_ccrs)
+              6.8. Failed interim CCRs (failed_interim_ccrs)
+              6.9. Failed final CCRs (failed_final_ccrs)
+              6.10. CCRs average response time (ccr_avg_response_time)
+              6.11. CCRs responses time (ccr_responses_time)
+              6.12. Billed seconds (billed_secs)
+              6.13. Killed calls (killed_calls)
+
+   List of Examples
+
+   1.1. hash_sizeparameter usage
+   1.2. interim_update_creditsparameter usage
+   1.3. timer_bufferparameter usage
+   1.4. ro_forced_peerparameter usage
+   1.5. ro_auth_expiryparameter usage
+   1.6. ro_auth_expiryparameter usage
+   1.7. cdp_event_latencyparameter usage
+   1.8. cdp_event_thresholdparameter usage
+   1.9. cdp_event_latency_logparameter usage
+   1.10. origin_hostparameter usage
+   1.11. origin_realmparameter usage
+   1.12. destination_hostparameter usage
+   1.13. destination_realmparameter usage
+   1.14. service_context_id_rootparameter usage
+   1.15. service_context_id_extparameter usage
+   1.16. service_context_id_mncparameter usage
+   1.17. service_context_id_mccparameter usage
+   1.18. service_context_id_releaseparameter usage
+   1.19. Ro_CCR
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Understanding Charging in the IP-Multimedia-Subsystem (IMS)
+
+        3.1. Offline Charging (Rf)
+        3.2. Online Charging (Ro)
+        3.3. Online Charging (Ro): A practical example
+
+   4. Parameters
+
+        4.1. hash_size(int)
+        4.2. interim_update_credits(int)
+        4.3. timer_buffer(int)
+        4.4. ro_forced_peer(string)
+        4.5. ro_auth_expiry(integer)
+        4.6. ro_auth_expiry(integer)
+        4.7. cdp_event_latency(integer)
+        4.8. cdp_event_threshold(integer)
+        4.9. cdp_event_latency_log(integer)
+        4.10. origin_host(string)
+        4.11. origin_realm(string)
+        4.12. destination_host(string)
+        4.13. destination_realm(string)
+        4.14. service_context_id_root(string)
+        4.15. service_context_id_ext(string)
+        4.16. service_context_id_mnc(string)
+        4.17. service_context_id_mcc(string)
+        4.18. service_context_id_release(string)
+
+   5. Functions
+
+        5.1. Ro_CCR(route_name, direction, charge_type, unit_type,
+                reservation_units)
+
+   6. Statistics
+
+        6.1. Initial CCRs (initial_ccrs)
+        6.2. Interim CCRs (interim_ccrs)
+        6.3. Final CCRs (final_ccrs)
+        6.4. Sucessful initial CCRs (successful_initial_ccrs)
+        6.5. Sucessful interim CCRs (successful_interim_ccrs)
+        6.6. Sucessful final CCRs (successful_final_ccrs)
+        6.7. Failed initial CCRs (failed_initial_ccrs)
+        6.8. Failed interim CCRs (failed_interim_ccrs)
+        6.9. Failed final CCRs (failed_final_ccrs)
+        6.10. CCRs average response time (ccr_avg_response_time)
+        6.11. CCRs responses time (ccr_responses_time)
+        6.12. Billed seconds (billed_secs)
+        6.13. Killed calls (killed_calls)
+
+1. Overview
+
+   This module contains all methods related to the IMS charging control
+   functions performed by an network element (e.g. a S-CSCF) over the Ro
+   interface. 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.
+
+   Please also refer to RFC 4006 (Diameter Credit-Control Application)
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The Following mouldes must be loaded before this module:
+     * Dialog_ng
+     * TM - Transaction Manager
+     * CDP - C Diameter Peer
+     * CDP_AVP - CDP AVP Applications
+
+2.2. External Libraries or Applications
+
+   This modules requires the internal IMS library.
+
+3. Understanding Charging in the IP-Multimedia-Subsystem (IMS)
+
+   3.1. Offline Charging (Rf)
+   3.2. Online Charging (Ro)
+   3.3. Online Charging (Ro): A practical example
+
+   Before each service usage, the charging system must be asked for
+   permission (credit authorization). The charging server must make a
+   decision: Either authorize or deny the session. For postpaid scenarios
+   this is fairly easy: The charging-server only needs to collect the
+   usage data for processing it at the end of the month. As no realtime
+   account updating is needed, this is often called "offline-charging".
+   For prepaid scenarios the charging server needs to know the user's
+   account balance and it will need to update the account in real-time.
+   This is often referred to as "online-charging".
+
+   Question: What is the double of the Radius? Answer: It's the Diameter!
+
+   As quite often, we use the Diameter-Protocol to do the Charging in the
+   IMS. And as quite often, IMS uses a huge bunch of acronyms to describe
+   the different interfaces: We call the diameter-interface for
+   offline-charging the "Rf"-interface and the interface for online
+   charging the "Ro"-interface.
+
+   Each system, that needs this credit authorization, have to be equipped
+   with a proper charging trigger, a so-called charging-trigger-function
+   (CTF) in order to communicate with the charging-server (also called
+   charging-function):
+   [charging1.png]
+
+3.1. Offline Charging (Rf)
+
+   For the offlinc charging (Rf), we have the following two
+   diameter-messages:
+     * ACR - Accounting Request
+     * ACA - Accounting Answer
+
+   Each request can have the following Accounting-Record-Type:
+     * START_RECORD - used to start an accounting session, typically when
+       the application receives a SIP 200 OK acknowledging an initial SIP
+       INVITE.
+     * INTERIM_RECORD - used to update a session, for example, in the case
+       of SIP RE-INVITE and/or UPDATE in the current SIP dialog.
+     * STOP_RECORD - used to stop an accounting session, for example, when
+       the application receives a SIP BYE message.
+     * EVENT_RECORD - used for event-based accounting, e.g. a short
+       message or similar
+
+3.2. Online Charging (Ro)
+
+   For online charging (Ro), this get's a little bit more complicated. The
+   charging function needs to perform credit control before allowing
+   resource usage. The prepaid subscriber needs to exist in the
+   charging-server and all activities must be monitored by the
+   charging-server. We must distinguish between the following two cases:
+     * Direct debiting - the amount is immediately deducted from the
+       user's account in one single transaction. This could be for example
+       a SMS or the ordering of a movie in case of Video-on-Demand.
+     * Unit reservation - an amount is reserved by the charging-server.
+       This is done, because the charging-server does not know yet, how
+       many units are needed to provide the service. During the session,
+       the used amount may be deducted and more units can be requested; at
+       the end of the session the used sessions are reported in the final
+       request. These sessions could be typically a voice- or video-call
+       or a Pay-TV session, if you pay per usage.
+
+   As a result, we have the following three scenarios:
+     * Immediate Event Charging (IEC) - used for simple Event-based
+       charging
+     * Event Charging with Unit Reservation (ECUR) (of type Event-based
+       charging)
+     * Session Charging with Unit Reservation (SCUR) (of type
+       Session-based charging)
+
+3.3. Online Charging (Ro): A practical example
+
+   But how does it look in reality? Let us make a more practical example:
+
+   Let us assume we have a subscriber, who has sufficient credit for 75
+   seconds of talking. The subscriber initiates a call; as we do not know,
+   how long the call will take, we start with requesting credit for 30
+   seconds (CCR-Request, we could request any duration, e.g. 2 hours, but
+   it would probably block other calls if we reserve all the required
+   credit).
+
+   The call proceeds, so after 30 seconds we send another CCR-Request with
+   the indication that we used the reserved 30 seconds and that we request
+   another 30 seconds. We reduce the account of the subscriber by 30
+   seconds, so he has a credit of 45 seconds. Since 45 seconds is more
+   than the requested 30 seconds, this second request can also easily be
+   accepted and another 30 seconds can be granted. After this request, the
+   account is at 45 seconds and we still (or again) have 30 seconds
+   reserved.
+
+   Meanwhile the subscriber initiates a second call. We try to request
+   again 30 seconds from the charging-server, but as our account is at 45
+   seconds of speaking time and since we reserved another 30 seconds for
+   the first call, we can only grant 15 seconds for the second call. The
+   last 15 seconds are now reserved for this subscriber; we have 45
+   seconds on the account of which 45 seconds are reserved.
+
+   Now the first call gets terminated: We only used 20 seconds from the
+   granted 30 seconds. So we decrease the account of the subscriber by 20
+   seconds and we reduce the amount of reserved units by 30. We have 25
+   seconds in the account and we have still reserved 15 seconds for the
+   second call.
+
+   As the second call is still proceeding, we will try to request another
+   30 seconds and we indicate, that we used the granted 15 seconds. The
+   account is deducted by 15 seconds (the used units) and we can grant
+   another 10 seconds for the second call, as this is the remains on the
+   account.
+
+   After 10 seconds, no more units can be granted, so the call is teared
+   down.
+
+   The following diagram is a graphical representation of the above
+   example:
+   [charging2.png]
+
+4. Parameters
+
+   4.1. hash_size(int)
+   4.2. interim_update_credits(int)
+   4.3. timer_buffer(int)
+   4.4. ro_forced_peer(string)
+   4.5. ro_auth_expiry(integer)
+   4.6. ro_auth_expiry(integer)
+   4.7. cdp_event_latency(integer)
+   4.8. cdp_event_threshold(integer)
+   4.9. cdp_event_latency_log(integer)
+   4.10. origin_host(string)
+   4.11. origin_realm(string)
+   4.12. destination_host(string)
+   4.13. destination_realm(string)
+   4.14. service_context_id_root(string)
+   4.15. service_context_id_ext(string)
+   4.16. service_context_id_mnc(string)
+   4.17. service_context_id_mcc(string)
+   4.18. service_context_id_release(string)
+
+4.1.  hash_size(int)
+
+   The size of the hash table internally used to keep the
+   Diameter-Ro-Session. A larger table is much faster but consumes more
+   memory. The hash size must be a power of two number.
+
+   IMPORTANT: If Ro-Session's information should be stored in a database,
+   a constant hash_size should be used, otherwise the restoring process
+   will not take place. If you really want to modify the hash_size you
+   must delete all table's rows before restarting the server.
+
+   Default value is 4096.
+
+   Example 1.1.  hash_sizeparameter usage
+... modparam("ims_charging", "hash_size", 1024)
+        ...
+
+4.2.  interim_update_credits(int)
+
+   How much credit should be requested interim request? At the start of
+   the call, we request the amout of seconds as per Command. For each
+   interim request, we would request credit for "interim_update_credits".
+
+   Default value is 30.
+
+   Example 1.2.  interim_update_creditsparameter usage
+... modparam("ims_charging",
+        "interim_update_credits", 600) ...
+
+4.3.  timer_buffer(int)
+
+   How many seconds before expiry of our credit should we request more
+   credit?
+
+   Default value is 8.
+
+   Example 1.3.  timer_bufferparameter usage
+... modparam("ims_charging", "timer_buffer", 10)
+        ...
+
+4.4.  ro_forced_peer(string)
+
+   This is the optional name of the origin host of the Diameter server
+   (typically a Charging Server). If not set then realm routing is used.
+
+   Default value is ''.
+
+   Example 1.4.  ro_forced_peerparameter usage
+... modparam("ims_charging", "ro_forced_peer",
+        "ocs.ims.smilecoms.com") ...
+
+4.5.  ro_auth_expiry(integer)
+
+   This is the expiry length in seconds of the initiated Diameter
+   sessions.
+
+   Default value is 7200.
+
+   Example 1.5.  ro_auth_expiryparameter usage
+... modparam("ims_charging", "ro_auth_expiry", 14400)
+        ...
+
+4.6.  ro_auth_expiry(integer)
+
+   This is the expiry length in seconds of the initiated Diameter
+   sessions.
+
+   Default value is 7200.
+
+   Example 1.6.  ro_auth_expiryparameter usage
+... modparam("ims_charging", "ro_auth_expiry", 14400)
+        ...
+
+4.7.  cdp_event_latency(integer)
+
+   This is a flag to determine whether or slow CDP responses should be
+   reported in the log file. 1 is enabled and 0 is disabled.
+
+   Default value is 1.
+
+   Example 1.7.  cdp_event_latencyparameter usage
+... modparam("ims_charging", "cdp_event_latency", 1)
+        ...
+
+4.8.  cdp_event_threshold(integer)
+
+   This time in milliseconds is the limit we should report a CDP response
+   as slow. i.e. if a CDP response exceeds this limit it will be reported
+   in the log file. This is only relevant is cdp_event_latency is enabled
+   (set to 0).
+
+   Default value is 500.
+
+   Example 1.8.  cdp_event_thresholdparameter usage
+... modparam("ims_charging", "cdp_event_threshold",
+        500) ...
+
+4.9.  cdp_event_latency_log(integer)
+
+   This time log level at which we should report slow CDP responses. 0 is
+   ERROR, 1 is WARN, 2 is INFO and 3 is DEBUG. This is only relevant is
+   cdp_event_latency is enabled (set to 0)
+
+   Default value is 0.
+
+   Example 1.9.  cdp_event_latency_logparameter usage
+... modparam("ims_charging", "cdp_event_latency_log",
+        1) ...
+
+4.10.  origin_host(string)
+
+   Origin host to be used in Diameter messages to charging-server.
+
+   Default value is "scscf.ims.smilecoms.com".
+
+   Example 1.10.  origin_hostparameter usage
+... modparam("ims_charging", "origin_host",
+        "scscf.kamailio-ims.org") ...
+
+4.11.  origin_realm(string)
+
+   Origin Realm to be used in Diameter messages to charging-server.
+
+   Default value is "ims.smilecome.com".
+
+   Example 1.11.  origin_realmparameter usage
+... modparam("ims_charging", "origin_realm",
+        "kamailio-ims.org") ...
+
+4.12.  destination_host(string)
+
+   Destination host to be used in Diameter messages to charging-server.
+
+   Default value is 5s.
+
+   Example 1.12.  destination_hostparameter usage
+... modparam("ims_charging", "destination_host",
+        "ocs.kamailio-ims.org") ...
+
+4.13.  destination_realm(string)
+
+   Destination realm to be used in Diameter messages to charging-server.
+
+   Default value is "ims.smilecoms.com".
+
+   Example 1.13.  destination_realmparameter usage
+... modparam("ims_charging", "destination_realm",
+        "kamailio-ims.org") ...
+
+4.14.  service_context_id_root(string)
+
+   This defines a root-element of the Service-Context-Id AVP used in the
+   diameter-message
+
+   The Service-Context-Id AVP is of type UTF8String (AVP Code 461) and
+   contains a unique identifier of the Diameter credit-control service
+   specific document that applies to the request (as defined in section
+   RFC 4006 4.1.2). This is an identifier allocated by the service
+   provider, by the service element manufacturer, or by a standardization
+   body, and MUST uniquely identify a given Diameter credit-control
+   service specific document. The format of the Service-Context-Id is:
+"service-context" "@" "domain" service-context =
+      Token
+
+   The Token is an arbitrary string of characters and digits.
+
+   'domain' represents the entity that allocated the Service-Context-Id.
+   It can be ietf.org, 3gpp.org, etc., if the identifier is allocated by a
+   standardization body, or it can be the FQDN of the service provider
+   (e.g., provider.example.com) or of the vendor (e.g.,
+   vendor.example.com) if the identifier is allocated by a private entity.
+
+   Service-specific documents that are for private use only (i.e., to one
+   provider's own use, where no interoperability is deemed useful) may
+   define private identifiers without need of coordination. However, when
+   interoperability is wanted, coordination of the identifiers via, for
+   example, publication of an informational RFC is RECOMMENDED in order to
+   make Service-Context-Id globally available.
+
+   Default value is "[email protected]".
+
+   Example 1.14.  service_context_id_rootparameter usage
+... modparam("ims_charging",
+        "service_context_id_root", "[email protected]") ...
+
+4.15.  service_context_id_ext(string)
+
+   This defines the extension of the Service-Context-Id AVP used in the
+   diameter-message.
+
+   Default value is "ext".
+
+   Example 1.15.  service_context_id_extparameter usage
+... modparam("ims_charging",
+        "service_context_id_ext", "ext2") ...
+
+4.16.  service_context_id_mnc(string)
+
+   This defines Mobile-Network-Code (MNC) of the Service-Context-Id AVP
+   used in the diameter-message.
+
+   Default value is "01".
+
+   Example 1.16.  service_context_id_mncparameter usage
+... modparam("ims_charging",
+        "service_context_id_mnc", "42") ...
+
+4.17.  service_context_id_mcc(string)
+
+   This defines Mobile-Country-Code (MCC) of the Service-Context-Id AVP
+   used in the diameter-message.
+
+   see https://en.wikipedia.org/wiki/Mobile_country_code_(MCC) for
+   details.
+
+   Default value is "001".
+
+   Example 1.17.  service_context_id_mccparameter usage
+... modparam("ims_charging",
+        "service_context_id_mcc", "262") ...
+
+4.18.  service_context_id_release(string)
+
+   This defines Release of the Service-Context-Id AVP used in the
+   diameter-message.
+
+   Default value is "8" (Release 8).
+
+   Example 1.18.  service_context_id_releaseparameter usage
+... modparam("ims_charging",
+        "service_context_id_release", "262") ...
+
+5. Functions
+
+   5.1. Ro_CCR(route_name, direction, charge_type, unit_type,
+          reservation_units)
+
+5.1.  Ro_CCR(route_name, direction, charge_type, unit_type,
+reservation_units)
+
+   Perform a CCR on Diameter Ro interface for Charging
+
+   Meaning of the parameters is as follows:
+     * route_nameroute to be executed upon reception of charging requests
+     * direction"orig"inating or "term"inating
+     * charge_type"IEC" = Immediate Event Charging, "ECUR" - Event
+       Charging with Unit Reservation or "SCUR" - Session Charging with
+       Unit Reservation.
+       (Note: At the moment only SCUR is supported)
+     * unit_typeTypes of the unit to be requested
+       (unused at the moment)
+     * reservation_unitshow many units (at the moment seconds) should be
+       reservated at the moment.
+
+   This function can be used from REQUEST_ROUTE.
+
+   This method is executed asynchronously. See example on how to retrieve
+   return value.
+
+   Example 1.19. Ro_CCR
+... xlog("L_DBG","Sending initial CCR Request for
+        call\n"); Ro_CCR("CHARGING_CCR_REPLY", "orig", "SCUR", "", "30"); }
+        route[CHARGING_CCR_REPLY] { xlog("L_DBG","cca_return code is $avp(s:cca_
+return_code)\n");
+        switch ($avp(s:cca_return_code)){ case 1: #success xlog("L_DBG", "CCR su
+ccess - will route
+        message\n"); route(Finalize_Orig); break; case -1: #failure xlog("L_ERR"
+, "CCR failure -
+        error response sent from module\n"); sl_send_reply("402","Payment requir
+ed"); break; case
+        -2: #error xlog("L_ERR", "CCR error - error response sent from module\n"
+);
+        sl_send_reply("500", "Charging Error"); break; default: xlog("L_ERR", "U
+nknown return code
+        from CCR: [$avp(s:cca_return_code)] \n"); break; } exit; } ...
+
+6. Statistics
+
+   6.1. Initial CCRs (initial_ccrs)
+   6.2. Interim CCRs (interim_ccrs)
+   6.3. Final CCRs (final_ccrs)
+   6.4. Sucessful initial CCRs (successful_initial_ccrs)
+   6.5. Sucessful interim CCRs (successful_interim_ccrs)
+   6.6. Sucessful final CCRs (successful_final_ccrs)
+   6.7. Failed initial CCRs (failed_initial_ccrs)
+   6.8. Failed interim CCRs (failed_interim_ccrs)
+   6.9. Failed final CCRs (failed_final_ccrs)
+   6.10. CCRs average response time (ccr_avg_response_time)
+   6.11. CCRs responses time (ccr_responses_time)
+   6.12. Billed seconds (billed_secs)
+   6.13. Killed calls (killed_calls)
+
+6.1. Initial CCRs (initial_ccrs)
+
+   The number of initial CCRs, i.e., the CCRs that were sent for the
+   initial INVITEs.
+
+6.2. Interim CCRs (interim_ccrs)
+
+   The number of CCRs sent within established sessions.
+
+6.3. Final CCRs (final_ccrs)
+
+   The number of CCRs sent to terminate a session.
+
+6.4. Sucessful initial CCRs (successful_initial_ccrs)
+
+   Initial CCRs that ended with DIAMETER_SUCCESS response code.
+
+6.5. Sucessful interim CCRs (successful_interim_ccrs)
+
+   Interim CCRs that ended with DIAMETER_SUCCESS response code.
+
+6.6. Sucessful final CCRs (successful_final_ccrs)
+
+   Final CCRs that ended with DIAMETER_SUCCESS response code.
+
+6.7. Failed initial CCRs (failed_initial_ccrs)
+
+   Initial CCRs that ended with no DIAMETER_SUCCESS response or with some
+   other error during processing.
+
+6.8. Failed interim CCRs (failed_interim_ccrs)
+
+   Interim CCRs that ended with no DIAMETER_SUCCESS response or with some
+   other error during processing.
+
+6.9. Failed final CCRs (failed_final_ccrs)
+
+   Final CCRs that ended with no DIAMETER_SUCCESS response or with some
+   other error during processing.
+
+6.10. CCRs average response time (ccr_avg_response_time)
+
+   Average CCA arrival time in milliseconds.
+
+6.11. CCRs responses time (ccr_responses_time)
+
+   Total CCA arrival time in milliseconds.
+
+6.12. Billed seconds (billed_secs)
+
+   Number of seconds billed in total.
+
+6.13. Killed calls (killed_calls)
+
+   Number of calls that were killed due to lack of credit.

+ 289 - 0
modules/ims_charging/Ro_data.c

@@ -0,0 +1,289 @@
+#include "Ro_data.h"
+#include "config.h"
+
+
+#define str_dup(dst,src,mem) \
+do {\
+	if ((src).len) {\
+		(dst).s = mem##_malloc((src).len);\
+		if (!(dst).s){\
+			LM_ERR("Error allocating %d bytes in %s!\n",(src).len,#mem);\
+			(dst).len = 0;\
+			goto out_of_memory;\
+		}\
+		memcpy((dst).s,(src).s,(src).len);\
+		(dst).len = (src).len;\
+	}else{\
+		(dst).s=0;(dst).len=0;\
+	}\
+} while (0)
+
+/**
+ * Frees a str content.
+ * @param x - the str to free
+ * @param mem - type of memory that the content is using (shm/pkg)
+ */
+#define str_free(x,mem) \
+do {\
+	if ((x).s) mem##_free((x).s);\
+	(x).s=0;(x).len=0;\
+} while(0)
+
+extern client_ro_cfg cfg;
+
+event_type_t * new_event_type(str * sip_method, str * event, uint32_t * expires) {
+    event_type_t * x = 0;
+
+    mem_new(x, sizeof (event_type_t), pkg);
+    if (sip_method && sip_method->s)
+        str_dup_ptr(x->sip_method, *sip_method, pkg);
+    if (event && event->s)
+        str_dup_ptr(x->event, *event, pkg);
+    if (expires && *expires != 0) {
+        mem_new(x->expires, sizeof (uint32_t), pkg);
+        *(x->expires) = *expires;
+    }
+    return x;
+
+out_of_memory:
+    LM_ERR("out of pkg memory\n");
+    event_type_free(x);
+    return NULL;
+}
+
+time_stamps_t * new_time_stamps(time_t *sip_request_timestamp, uint32_t *sip_request_timestamp_fraction, time_t *sip_response_timestamp, uint32_t *sip_response_timestamp_fraction) {
+
+    time_stamps_t * x = 0;
+
+    mem_new(x, sizeof (time_stamps_t), pkg);
+
+    if (sip_request_timestamp && *sip_request_timestamp > 0) {
+        mem_new(x->sip_request_timestamp, sizeof (time_t), pkg);
+        *(x->sip_request_timestamp) = *sip_request_timestamp;
+    }
+
+    if (sip_request_timestamp_fraction && *sip_request_timestamp_fraction > 0) {
+        mem_new(x->sip_request_timestamp_fraction, sizeof (uint32_t), pkg);
+        *(x->sip_request_timestamp_fraction) = *sip_request_timestamp_fraction;
+    }
+
+    if (sip_response_timestamp && *sip_response_timestamp > 0) {
+        mem_new(x->sip_response_timestamp, sizeof (time_t), pkg);
+        *(x->sip_response_timestamp) = *sip_response_timestamp;
+    }
+
+    if (sip_response_timestamp_fraction && *sip_response_timestamp_fraction > 0) {
+        mem_new(x->sip_response_timestamp_fraction, sizeof (uint32_t), pkg);
+        *(x->sip_response_timestamp_fraction) = *sip_response_timestamp_fraction;
+    }
+
+
+    return x;
+
+out_of_memory:
+    LM_ERR("out of pkg memory\n");
+    time_stamps_free(x);
+    return 0;
+}
+
+ims_information_t * new_ims_information(event_type_t * event_type, time_stamps_t * time_stamps, str * user_session_id, str * outgoing_session_id, str * calling_party, str * called_party, str * icid, str * orig_ioi, str * term_ioi, int node_role) {
+
+    str_list_slot_t *sl = 0;
+    ims_information_t *x = 0;
+    ioi_list_element_t * ioi_elem = 0;
+
+    mem_new(x, sizeof (ims_information_t), pkg);
+
+    x->event_type = event_type;
+    x->time_stamps = time_stamps;
+
+    mem_new(x->role_of_node, sizeof (int32_t), pkg);
+    *(x->role_of_node) = node_role;
+
+    //x->node_functionality = cfg.node_func;
+
+    if (outgoing_session_id && outgoing_session_id->s)
+        str_dup_ptr(x->outgoing_session_id, *outgoing_session_id, pkg);
+
+    if (user_session_id && user_session_id->s)
+        str_dup_ptr(x->user_session_id, *user_session_id, pkg);
+
+    if (calling_party && calling_party->s) {
+        mem_new(sl, sizeof (str_list_slot_t), pkg);
+        str_dup(sl->data, *calling_party, pkg);
+        WL_APPEND(&(x->calling_party_address), sl);
+    }
+
+    if (called_party && called_party->s)
+        str_dup_ptr(x->called_party_address, *called_party, pkg);
+
+    //WL_FREE_ALL(&(x->called_asserted_identity),str_list_t,pkg);
+    //str_free_ptr(x->requested_party_address,pkg);
+
+    if ((orig_ioi && orig_ioi->s) || (term_ioi && term_ioi->s)) {
+        mem_new(ioi_elem, sizeof (ioi_list_element_t), pkg);
+        if (orig_ioi)
+            str_dup_ptr_ptr(ioi_elem->info.originating_ioi, orig_ioi, pkg);
+        if (term_ioi)
+            str_dup_ptr_ptr(ioi_elem->info.terminating_ioi, term_ioi, pkg);
+    }
+
+    if (icid && icid->s)
+        str_dup_ptr(x->icid, *icid, pkg);
+
+    return x;
+
+out_of_memory:
+    LM_ERR("out of pkg memory\n");
+    ims_information_free(x);
+    return NULL;
+}
+
+service_information_t * new_service_information(ims_information_t * ims_info, subscription_id_t * subscription) {
+    service_information_t * x = 0;
+    subscription_id_list_element_t * sl = 0;
+
+    mem_new(x, sizeof (service_information_t), pkg);
+
+    x->ims_information = ims_info;
+    if (subscription) {
+        mem_new(sl, sizeof (subscription_id_list_element_t), pkg);
+        subscription_id_list_t_copy(&(sl->s), subscription, pkg);
+        WL_APPEND(&(x->subscription_id), sl);
+    }
+
+    return x;
+
+out_of_memory:
+    LM_ERR("new service information: out of pkg memory\n");
+    service_information_free(x);
+    return 0;
+}
+
+Ro_CCR_t * new_Ro_CCR(int32_t acc_record_type, str * user_name, ims_information_t * ims_info, subscription_id_t * subscription) {
+
+
+    Ro_CCR_t *x = 0;
+
+    service_information_t * service_info = 0;
+
+    mem_new(x, sizeof (Ro_CCR_t), pkg);
+
+    str_dup(x->origin_host, cfg.origin_host, pkg);
+    str_dup(x->origin_realm, cfg.origin_realm, pkg);
+    str_dup(x->destination_realm, cfg.destination_realm, pkg);
+    x->acct_record_type = acc_record_type;
+
+    if (user_name) {
+        str_dup_ptr_ptr(x->user_name, user_name, pkg);
+    }
+
+    if (cfg.service_context_id && cfg.service_context_id->s)
+        str_dup_ptr(x->service_context_id, *(cfg.service_context_id), pkg);
+
+    if (ims_info)
+        if (!(service_info = new_service_information(ims_info, subscription)))
+            goto error;
+
+    x->service_information = service_info;
+    service_info = 0;
+
+    return x;
+
+out_of_memory:
+    LM_ERR("out of pkg memory\n");
+error:
+    Ro_free_CCR(x);
+    service_information_free(service_info);
+
+    return 0;
+}
+
+void event_type_free(event_type_t *x) {
+    if (!x) return;
+    str_free_ptr(x->sip_method, pkg);
+    str_free_ptr(x->event, pkg);
+    mem_free(x->expires, pkg);
+    mem_free(x, pkg);
+}
+
+void time_stamps_free(time_stamps_t *x) {
+    if (!x) return;
+    mem_free(x->sip_request_timestamp, pkg);
+    mem_free(x->sip_request_timestamp_fraction, pkg);
+    mem_free(x->sip_response_timestamp, pkg);
+    mem_free(x->sip_response_timestamp_fraction, pkg);
+    mem_free(x, pkg);
+}
+
+void ims_information_free(ims_information_t *x) {
+    if (!x) return;
+
+    event_type_free(x->event_type);
+
+    mem_free(x->role_of_node, pkg);
+    str_free_ptr(x->user_session_id, pkg);
+    str_free_ptr(x->outgoing_session_id, pkg);
+
+    WL_FREE_ALL(&(x->calling_party_address), str_list_t, pkg);
+    str_free_ptr(x->called_party_address, pkg);
+    WL_FREE_ALL(&(x->called_asserted_identity), str_list_t, pkg);
+    str_free_ptr(x->requested_party_address, pkg);
+
+    time_stamps_free(x->time_stamps);
+
+    WL_FREE_ALL(&(x->as_info), as_info_list_t, pkg);
+
+    WL_FREE_ALL(&(x->ioi), ioi_list_t, pkg);
+    str_free_ptr(x->icid, pkg);
+
+    str_free_ptr(x->service_id, pkg);
+
+    WL_FREE_ALL(&(x->service_specific_info), service_specific_info_list_t, pkg);
+
+    mem_free(x->cause_code, pkg);
+
+    mem_free(x, pkg);
+}
+
+void service_information_free(service_information_t *x) {
+    if (!x) return;
+
+    WL_FREE_ALL(&(x->subscription_id), subscription_id_list_t, pkg);
+    ims_information_free(x->ims_information);
+
+    mem_free(x, pkg);
+}
+
+void Ro_free_CCR(Ro_CCR_t *x) {
+    if (!x) return;
+
+    str_free(x->origin_host, pkg);
+    str_free(x->origin_realm, pkg);
+    str_free(x->destination_realm, pkg);
+
+    str_free_ptr(x->user_name, pkg);
+    mem_free(x->acct_interim_interval, pkg);
+    mem_free(x->origin_state_id, pkg);
+    mem_free(x->event_timestamp, pkg);
+
+    str_free_ptr(x->service_context_id, pkg);
+
+    service_information_free(x->service_information);
+
+    mem_free(x, pkg);
+}
+
+void Ro_free_CCA(Ro_CCA_t *x) {
+    if (!x) return;
+
+    if (x->mscc->final_unit_action) {
+        mem_free(x->mscc->final_unit_action, pkg);
+    }
+    mem_free(x->mscc->granted_service_unit, pkg);
+    mem_free(x->mscc, pkg);
+    mem_free(x, pkg);
+}
+
+
+

+ 408 - 0
modules/ims_charging/Ro_data.h

@@ -0,0 +1,408 @@
+#ifndef __CDF_Ro_data_H
+#define __CDF_Ro_data_H
+
+#include "../cdp/diameter.h"
+#include "../cdp/diameter_epc.h"
+
+
+/**
+ * Allocate and blank a memory area
+ */
+#define mem_new(dst,len,mem) \
+do {\
+	if (!(len)) (dst)=0;\
+	else {\
+		(dst) = mem##_malloc(len);\
+		if (!(dst)) {\
+			LM_ERR("Error allocating %ld bytes in %s!\n",(long int)len,#mem);\
+			goto out_of_memory;\
+		}\
+		bzero((dst),(len));\
+	}\
+} while(0)
+
+#define struct_new(dst,type,mem) \
+		men_new(dst,sizeof(type),mem)
+
+
+/**
+ * Duplicate an arbitrary memory area
+ */
+#define mem_dup(dst,src,len,mem) \
+do {\
+	if (!(src)||!(len)) (dst)=0;\
+	else {\
+		(dst) = mem##_malloc(len);\
+		if (!(dst)) {\
+			LM_ERR("Error allocating %ld bytes in %s!\n",(long int)len,#mem);\
+			goto out_of_memory;\
+		}\
+		memcpy((dst),(src),len);\
+	}\
+} while(0)
+
+
+/**
+ * Free an arbitrary memory area
+ */
+#define mem_free(x,mem) \
+do {\
+	if (x) {\
+		mem##_free(x);\
+		x=0;\
+	}\
+} while(0)
+
+
+#define str_dup_ptr(dst,src,mem) \
+do {\
+	dst = mem##_malloc(sizeof(str));\
+	if ((src).len) {\
+		(dst)->s = mem##_malloc((src).len);\
+		if (!(dst)->s){\
+			LM_ERR("Error allocating %d bytes in %s!\n",(src).len,#mem);\
+			mem##_free(dst);\
+			goto out_of_memory;\
+		}\
+		memcpy((dst)->s,(src).s,(src).len);\
+		(dst)->len = (src).len;\
+	}else{\
+		(dst)->s=0;(dst)->len=0;\
+	}\
+} while (0)
+
+#define str_dup_ptr_ptr(dst,src,mem) \
+do {\
+	dst = mem##_malloc(sizeof(str));\
+	if ((src) && (src)->len) {\
+		(dst)->s = mem##_malloc((src)->len);\
+		if (!(dst)->s){\
+			LM_ERR("Error allocating %d bytes in %s!\n",(src)->len,#mem);\
+			mem##_free(dst);\
+			goto out_of_memory;\
+		}\
+		memcpy((dst)->s,(src)->s,(src)->len);\
+		(dst)->len = (src)->len;\
+	}else{\
+		(dst)->s=0;(dst)->len=0;\
+	}\
+} while (0)
+
+
+#define str_free_ptr(x,mem) \
+do {\
+	if (x) {\
+		if ((x)->s) mem##_free((x)->s);\
+		mem##_free((x));\
+	}\
+} while(0)
+
+/**
+ * list operations 
+ */
+
+#define WL_APPEND(list,add)                                                      \
+do {                                                                             \
+  (add)->next = NULL;															 \
+  (add)->prev = (list)->tail;													 \
+  if ((list)->tail) ((list)->tail)->next=(add);									 \
+  else (list)->head = (add);                                                     \
+  (list)->tail=(add);                                                        	 \
+} while (0)
+
+#define WL_FREE(el,list_type,mem)												\
+	list_type##_free(el,mem)
+
+#define WL_FREE_ALL(list,list_type,mem)\
+do {\
+	struct _##list_type##_slot *el,*nel;\
+	for(el=(list)->head;el;el=nel){\
+		nel = el->next;\
+		WL_FREE(el,list_type,mem);\
+	}\
+	(list)->head=0;\
+	(list)->tail=0;\
+} while(0)
+
+/* List of str */
+
+typedef struct _str_list_t_slot {
+    str data;
+    struct _str_list_t_slot *prev, *next;
+} str_list_slot_t;
+
+typedef struct {
+    str_list_slot_t *head, *tail;
+} str_list_t;
+
+#define str_list_t_free(x,mem) \
+do{\
+	if (x) {\
+		str_free((x)->data,mem);\
+		mem##_free(x);\
+		(x)=0;\
+	}\
+}while(0)
+
+
+#define str_list_t_copy(dst,src,mem) \
+	str_dup((dst)->data,(src)->data,mem)
+
+typedef struct {
+    str *sip_method;
+    str *event;
+    uint32_t *expires;
+} event_type_t;
+
+typedef struct {
+    time_t *sip_request_timestamp;
+    uint32_t *sip_request_timestamp_fraction;
+    time_t *sip_response_timestamp;
+    uint32_t *sip_response_timestamp_fraction;
+} time_stamps_t;
+
+typedef struct {
+    str *application_server;
+    str_list_t application_provided_called_party_address;
+} as_info_t;
+
+typedef struct _as_info_list_t_slot {
+    as_info_t info;
+    struct _as_info_list_t_slot *next, *prev;
+} as_info_list_element_t;
+
+typedef struct {
+    as_info_list_element_t *head, *tail;
+} as_info_list_t;
+
+
+#define as_info_list_t_free(x,mem) \
+do{\
+	str_list_slot_t *y=0,*z;\
+	if (x) {\
+		str_free_ptr((x)->info.application_server,mem);\
+		for(y=(x)->info.application_provided_called_party_address.head;y;y=z){\
+			z=y->next;\
+			str_free(y->data,mem);\
+			mem_free(y,mem);\
+		}\
+		mem##_free(x);\
+		(x) = 0;\
+	}\
+}while(0)
+
+#define as_info_list_t_copy(dst,src,mem) \
+do {\
+	str_dup_ptr_ptr((dst)->info.application_server,(src)->info.application_server,mem);\
+	WL_DUP_ALL(&((dst)->info.application_provided_called_party_address),&((src)->info.application_provided_called_party_address),str_list_t,mem);\
+} while(0)
+
+typedef struct {
+    str *originating_ioi;
+    str *terminating_ioi;
+} ioi_t;
+
+typedef struct _ioi_list_t_slot {
+    ioi_t info;
+    struct _ioi_list_t_slot *next, *prev;
+} ioi_list_element_t;
+
+typedef struct {
+    ioi_list_element_t *head, *tail;
+} ioi_list_t;
+
+
+#define ioi_list_t_free(x,mem) \
+do{\
+	if (x) {\
+		str_free_ptr((x)->info.originating_ioi,mem);\
+		str_free_ptr((x)->info.terminating_ioi,mem);\
+		mem##_free(x);\
+		(x) = 0;\
+	}\
+}while(0)
+
+#define ioi_list_t_copy(dst,src,mem) \
+do {\
+	str_dup_ptr_ptr((dst)->info.originating_ioi,(src)->info.originating_ioi,mem);\
+	str_dup_ptr_ptr((dst)->info.terminating_ioi,(src)->info.terminating_ioi,mem);\
+} while(0)
+
+typedef struct {
+    str *data;
+    uint32_t *type;
+} service_specific_info_t;
+
+typedef struct _service_specific_info_list_t_slot {
+    service_specific_info_t info;
+    struct _service_specific_info_list_t_slot *next, *prev;
+} service_specific_info_list_element_t;
+
+typedef struct {
+    service_specific_info_list_element_t *head, *tail;
+} service_specific_info_list_t;
+
+
+#define service_specific_info_list_t_free(x,mem) \
+do{\
+	if (x) {\
+		str_free_ptr((x)->info.data,mem);\
+		mem_free((x)->info.type,mem);\
+		mem##_free(x);\
+		(x) = 0;\
+	}\
+}while(0)
+
+#define service_specific_info_list_t_copy(dst,src,mem) \
+do {\
+	str_dup_ptr_ptr((dst)->info.data,(src)->info.data,mem);\
+	mem_dup((dst)->info.type,(src)->info.type,sizeof(uint32_t),mem);\
+} while(0)
+
+typedef struct {
+    event_type_t *event_type;
+
+    int32_t *role_of_node;
+    int32_t node_functionality;
+
+    str *user_session_id;
+    str *outgoing_session_id;
+
+    str_list_t calling_party_address;
+    str *called_party_address;
+    str_list_t called_asserted_identity;
+    str * requested_party_address;
+
+    time_stamps_t *time_stamps;
+
+    as_info_list_t as_info;
+
+    ioi_list_t ioi;
+    str *icid;
+
+    str *service_id;
+
+    service_specific_info_list_t service_specific_info;
+
+    int32_t *cause_code;
+
+} ims_information_t;
+
+typedef enum {
+    Subscription_Type_MSISDN = AVP_EPC_Subscription_Id_Type_End_User_E164,
+    Subscription_Type_IMSI = AVP_EPC_Subscription_Id_Type_End_User_IMSI,
+    Subscription_Type_IMPU = AVP_EPC_Subscription_Id_Type_End_User_SIP_URI,
+    Subscription_Type_NAI = AVP_EPC_Subscription_Id_Type_End_User_NAI,
+    Subscription_Type_IMPI = AVP_EPC_Subscription_Id_Type_End_User_Private,
+} subscription_id_type_e;
+
+typedef struct _subscription_id_t {
+    int32_t type;
+    str id;
+} subscription_id_t;
+
+typedef struct _subscription_id_list_t_slot {
+    subscription_id_t s;
+    struct _subscription_id_list_t_slot *next, *prev;
+} subscription_id_list_element_t;
+
+typedef struct {
+    subscription_id_list_element_t *head, *tail;
+} subscription_id_list_t;
+
+
+#define subscription_id_list_t_free(x,mem) \
+do{\
+	if (x) {\
+		str_free((x)->s.id,mem);\
+		mem##_free(x);\
+		(x) = 0;\
+	}\
+}while(0)
+
+#define subscription_id_list_t_copy(dst,src,mem) \
+do {\
+	(dst)->type = (src)->type;\
+	str_dup((dst)->id,(src)->id,mem);\
+} while(0)
+
+typedef struct {
+    subscription_id_list_t subscription_id;
+
+    ims_information_t *ims_information;
+
+} service_information_t;
+
+typedef struct {
+    str origin_host;
+    str origin_realm;
+    str destination_realm;
+
+    int32_t acct_record_type;
+    uint32_t acct_record_number;
+
+    str *user_name;
+    uint32_t *acct_interim_interval;
+    uint32_t *origin_state_id;
+    time_t *event_timestamp;
+
+    str *service_context_id;
+
+    service_information_t *service_information;
+} Ro_CCR_t;
+
+typedef struct {
+    uint32_t cc_time;
+    uint32_t cc_total_octets;
+    uint32_t cc_input_octets;
+    uint32_t cc_output_octets;
+} granted_services_unit_t;
+
+typedef struct {
+    uint32_t action;
+} final_unit_indication_t;
+
+typedef struct {
+    granted_services_unit_t *granted_service_unit;
+    uint32_t validity_time;
+    final_unit_indication_t *final_unit_action;
+} multiple_services_credit_control_t;
+
+typedef struct {
+    uint32_t resultcode;
+    uint32_t cc_request_type;
+    uint32_t cc_request_number;
+    multiple_services_credit_control_t *mscc;
+} Ro_CCA_t;
+
+event_type_t * new_event_type(str * sip_method,
+        str * event,
+        uint32_t * expires);
+
+time_stamps_t * new_time_stamps(time_t *sip_request_timestamp,
+        uint32_t *sip_request_timestamp_fraction,
+        time_t *sip_response_timestamp,
+        uint32_t *sip_response_timestamp_fraction);
+
+ims_information_t * new_ims_information(event_type_t * event_type,
+        time_stamps_t * time_stamps,
+        str * user_session_id,
+        str * outgoing_session_id,
+        str * calling_party,
+        str * called_party,
+        str * icid,
+        str * orig_ioi,
+        str * term_ioi,
+        int node_role);
+
+void event_type_free(event_type_t *x);
+void time_stamps_free(time_stamps_t *x);
+void ims_information_free(ims_information_t *x);
+void service_information_free(service_information_t *x);
+
+Ro_CCR_t * new_Ro_CCR(int32_t acc_record_type, str * user_name, ims_information_t * ims_info, subscription_id_t * subscription);
+void Ro_free_CCR(Ro_CCR_t *x);
+void Ro_free_CCA(Ro_CCA_t *x);
+
+
+#endif /* __CDF_Ro_data_H */

+ 328 - 0
modules/ims_charging/ccr.c

@@ -0,0 +1,328 @@
+#include "../cdp_avp/mod_export.h"
+
+#include "ccr.h"
+#include "Ro_data.h"
+
+extern cdp_avp_bind_t *cdp_avp;
+
+int Ro_write_event_type_avps(AAA_AVP_LIST * avp_list, event_type_t * x) {
+    AAA_AVP_LIST aList = {0, 0};
+
+    if (x->sip_method) {
+        if (!cdp_avp->epcapp.add_SIP_Method(&aList, *(x->sip_method), AVP_DUPLICATE_DATA))
+            goto error;
+    }
+
+    if (x->event)
+        if (!cdp_avp->epcapp.add_Event(&aList, *(x->event), 0))
+            goto error;
+
+    if (x->expires)
+        if (!cdp_avp->epcapp.add_Expires(avp_list, *(x->expires)))
+            goto error;
+
+    if (!cdp_avp->epcapp.add_Event_Type(avp_list, &aList, AVP_FREE_DATA))	//TODO: used to be DONT FREE
+        goto error;
+
+    return 1;
+error:
+    cdp_avp->cdp->AAAFreeAVPList(&aList);
+    LM_ERR("error while adding event type avps\n");
+    return 0;
+}
+
+int Ro_write_time_stamps_avps(AAA_AVP_LIST * avp_list, time_stamps_t* x) {
+    AAA_AVP_LIST aList = {0, 0};
+
+    if (x->sip_request_timestamp)
+        if (!cdp_avp->epcapp.add_SIP_Request_Timestamp(&aList, *(x->sip_request_timestamp)))
+            goto error;
+
+    if (x->sip_request_timestamp_fraction)
+        if (!cdp_avp->epcapp.add_SIP_Request_Timestamp_Fraction(&aList,
+                *(x->sip_request_timestamp_fraction)))
+            goto error;
+
+    if (x->sip_response_timestamp)
+        if (!cdp_avp->epcapp.add_SIP_Response_Timestamp(&aList, *(x->sip_response_timestamp)))
+            goto error;
+
+    if (x->sip_response_timestamp_fraction)
+        if (!cdp_avp->epcapp.add_SIP_Response_Timestamp_Fraction(&aList,
+                *(x->sip_response_timestamp_fraction)))
+            goto error;
+
+    if (!cdp_avp->epcapp.add_Time_Stamps(avp_list, &aList, AVP_FREE_DATA))	//used to be DONT FREE
+        goto error;
+
+
+    return 1;
+error:
+    cdp_avp->cdp->AAAFreeAVPList(&aList);
+    LM_ERR("error while adding time stamps avps\n");
+
+    return 0;
+}
+
+int Ro_write_ims_information_avps(AAA_AVP_LIST * avp_list, ims_information_t* x) {
+    str_list_slot_t * sl = 0;
+    AAA_AVP_LIST aList = {0, 0};
+    AAA_AVP_LIST aList2 = {0, 0};
+    service_specific_info_list_element_t * info = 0;
+    ioi_list_element_t * ioi_elem = 0;
+
+    if (x->event_type)
+        if (!Ro_write_event_type_avps(&aList2, x->event_type))
+            goto error;
+    if (x->role_of_node)
+        if (!cdp_avp->epcapp.add_Role_Of_Node(&aList2, *(x->role_of_node))) goto error;
+
+    if (!cdp_avp->epcapp.add_Node_Functionality(&aList2, x->node_functionality))
+        goto error;
+
+    if (x->user_session_id)
+        if (!cdp_avp->epcapp.add_User_Session_Id(&aList2, *(x->user_session_id), 0))
+            goto error;
+
+    for (sl = x->calling_party_address.head; sl; sl = sl->next) {
+        if (!cdp_avp->epcapp.add_Calling_Party_Address(&aList2, sl->data, 0))
+            goto error;
+    }
+
+    if (x->called_party_address)
+        if (!cdp_avp->epcapp.add_Called_Party_Address(&aList2, *(x->called_party_address), 0))
+            goto error;
+
+    for (sl = x->called_asserted_identity.head; sl; sl = sl->next) {
+        if (!cdp_avp->epcapp.add_Called_Asserted_Identity(&aList2, sl->data, 0))
+            goto error;
+    }
+
+    if (x->requested_party_address)
+        if (!cdp_avp->epcapp.add_Requested_Party_Address(&aList2, *(x->requested_party_address), 0))
+            goto error;
+    if (x->time_stamps)
+        if (!Ro_write_time_stamps_avps(&aList2, x->time_stamps))
+            goto error;
+
+    for (ioi_elem = x->ioi.head; ioi_elem; ioi_elem = ioi_elem->next) {
+
+        if (ioi_elem->info.originating_ioi)
+            if (!cdp_avp->epcapp.add_Originating_IOI(&aList, *(ioi_elem->info.originating_ioi), 0))
+                goto error;
+
+        if (ioi_elem->info.terminating_ioi)
+            if (!cdp_avp->epcapp.add_Terminating_IOI(&aList, *(ioi_elem->info.terminating_ioi), 0))
+                goto error;
+
+        if (!cdp_avp->epcapp.add_Inter_Operator_Identifier(&aList2, &aList, 0))
+            goto error;
+        aList.head = aList.tail = 0;
+    }
+
+    if (x->icid)
+        if (!cdp_avp->epcapp.add_IMS_Charging_Identifier(&aList2, *(x->icid), 0))
+            goto error;
+
+    if (x->service_id)
+        if (!cdp_avp->epcapp.add_Service_ID(&aList2, *(x->service_id), 0))
+            goto error;
+
+    for (info = x->service_specific_info.head; info; info = info->next) {
+
+        if (info->info.data)
+            if (!cdp_avp->epcapp.add_Service_Specific_Data(&aList, *(info->info.data), 0))
+                goto error;
+        if (info->info.type)
+            if (!cdp_avp->epcapp.add_Service_Specific_Type(&aList, *(info->info.type)))
+                goto error;
+
+        if (!cdp_avp->epcapp.add_Service_Specific_Info(&aList2, &aList, 0))
+            goto error;
+        aList.head = aList.tail = 0;
+    }
+
+    if (x->cause_code)
+        if (!cdp_avp->epcapp.add_Cause_Code(&aList2, *(x->cause_code)))
+            goto error;
+
+    if (!cdp_avp->epcapp.add_IMS_Information(avp_list, &aList2, AVP_FREE_DATA))//TODO check why not DONT FREE DATA
+        goto error;
+
+    return 1;
+error:
+    /*free aList*/
+    cdp_avp->cdp->AAAFreeAVPList(&aList);
+    cdp_avp->cdp->AAAFreeAVPList(&aList2);
+    LM_ERR("could not add ims information avps\n");
+    return 0;
+}
+
+int Ro_write_service_information_avps(AAA_AVP_LIST * avp_list, service_information_t* x) {
+    subscription_id_list_element_t * elem = 0;
+    AAA_AVP_LIST aList = {0, 0};
+
+    for (elem = x->subscription_id.head; elem; elem = elem->next) {
+
+        if (!cdp_avp->ccapp.add_Subscription_Id_Group(&aList, elem->s.type, elem->s.id, 0))
+            goto error;
+
+    }
+
+    if (x->ims_information)
+        if (!Ro_write_ims_information_avps(&aList, x->ims_information))
+            goto error;
+
+    if (!cdp_avp->epcapp.add_Service_Information(avp_list, &aList, AVP_FREE_DATA)) //TODO: use to be dont free
+        goto error;
+
+    return 1;
+error:
+    cdp_avp->cdp->AAAFreeAVPList(&aList);
+    return 0;
+}
+
+AAAMessage * Ro_write_CCR_avps(AAAMessage * ccr, Ro_CCR_t* x) {
+
+    if (!ccr) return 0;
+
+    if (!cdp_avp->base.add_Origin_Host(&(ccr->avpList), x->origin_host, 0)) goto error;
+
+    if (!cdp_avp->base.add_Origin_Realm(&(ccr->avpList), x->origin_realm, 0)) goto error;
+    if (!cdp_avp->base.add_Destination_Realm(&(ccr->avpList), x->destination_realm, 0)) goto error;
+
+    if (!cdp_avp->base.add_Accounting_Record_Type(&(ccr->avpList), x->acct_record_type)) goto error;
+    if (!cdp_avp->base.add_Accounting_Record_Number(&(ccr->avpList), x->acct_record_number)) goto error;
+
+    if (x->user_name)
+        if (!cdp_avp->base.add_User_Name(&(ccr->avpList), *(x->user_name), AVP_DUPLICATE_DATA)) goto error;
+
+    if (x->acct_interim_interval)
+        if (!cdp_avp->base.add_Acct_Interim_Interval(&(ccr->avpList), *(x->acct_interim_interval))) goto error;
+
+    if (x->origin_state_id)
+        if (!cdp_avp->base.add_Origin_State_Id(&(ccr->avpList), *(x->origin_state_id))) goto error;
+
+    if (x->event_timestamp)
+        if (!cdp_avp->base.add_Event_Timestamp(&(ccr->avpList), *(x->event_timestamp))) goto error;
+
+    if (x->service_context_id)
+        if (!cdp_avp->ccapp.add_Service_Context_Id(&(ccr->avpList), *(x->service_context_id), 0)) goto error;
+
+    if (x->service_information)
+        if (!Ro_write_service_information_avps(&(ccr->avpList), x->service_information))
+            goto error;
+    return ccr;
+error:
+    cdp_avp->cdp->AAAFreeMessage(&ccr);
+    return 0;
+}
+
+AAAMessage *Ro_new_ccr(AAASession * session, Ro_CCR_t * ro_ccr_data) {
+
+    AAAMessage * ccr = 0;
+    ccr = cdp_avp->cdp->AAACreateRequest(IMS_Ro, Diameter_CCR, Flag_Proxyable, session);
+    if (!ccr) {
+        LM_ERR("could not create CCR\n");
+        return 0;
+    }
+  
+    ccr = Ro_write_CCR_avps(ccr, ro_ccr_data);
+
+    return ccr;
+}
+
+Ro_CCA_t *Ro_parse_CCA_avps(AAAMessage *cca) {
+    if (!cca)
+        return 0;
+
+    Ro_CCA_t *ro_cca_data = 0;
+    mem_new(ro_cca_data, sizeof (Ro_CCR_t), pkg);
+    multiple_services_credit_control_t *mscc = 0;
+    mem_new(mscc, sizeof (multiple_services_credit_control_t), pkg);
+    granted_services_unit_t *gsu = 0;
+    mem_new(gsu, sizeof (granted_services_unit_t), pkg);
+    final_unit_indication_t *fui = 0;
+    mem_new(fui, sizeof (final_unit_indication_t), pkg);
+    mscc->granted_service_unit = gsu;
+    mscc->final_unit_action = fui;
+
+    mscc->final_unit_action->action = -1;
+
+    AAA_AVP_LIST* avp_list = &cca->avpList;
+    AAA_AVP_LIST mscc_avp_list;
+    AAA_AVP_LIST* mscc_avp_list_ptr;
+
+    AAA_AVP *avp = avp_list->head;
+    unsigned int x;
+    while (avp != NULL) {
+        switch (avp->code) {
+            case AVP_CC_Request_Type:
+                x = get_4bytes(avp->data.s);
+                ro_cca_data->cc_request_type = x;
+                break;
+            case AVP_CC_Request_Number:
+                x = get_4bytes(avp->data.s);
+                ro_cca_data->cc_request_number = x;
+                break;
+            case AVP_Multiple_Services_Credit_Control:
+                mscc_avp_list = cdp_avp->cdp->AAAUngroupAVPS(avp->data);
+                mscc_avp_list_ptr = &mscc_avp_list;
+                AAA_AVP *mscc_avp = mscc_avp_list_ptr->head;
+                while (mscc_avp != NULL) {
+                    LM_DBG("MSCC AVP code is [%i] and data length is [%i]", mscc_avp->code, mscc_avp->data.len);
+                    switch (mscc_avp->code) {
+                            AAA_AVP_LIST y;
+                            AAA_AVP *z;
+                        case AVP_Granted_Service_Unit:
+                            y = cdp_avp->cdp->AAAUngroupAVPS(mscc_avp->data);
+                            z = y.head;
+                            while (z) {
+                                switch (z->code) {
+                                    case AVP_CC_Time:
+                                        mscc->granted_service_unit->cc_time = get_4bytes(z->data.s);
+                                        break;
+                                    default:
+                                        LM_ERR("Unsupported Granted Service Unit with code:[%d]\n", z->code);
+                                }
+                                z = z->next;
+                            }
+                            cdp_avp->cdp->AAAFreeAVPList(&y);
+                            break;
+                        case AVP_Validity_Time:
+                            mscc->validity_time = get_4bytes(mscc_avp->data.s);
+                            break;
+                        case AVP_Final_Unit_Indication:
+                            y = cdp_avp->cdp->AAAUngroupAVPS(mscc_avp->data);
+                            z = y.head;
+                            while (z) {
+                                switch (z->code) {
+                                    case AVP_Final_Unit_Action:
+                                        mscc->final_unit_action->action = get_4bytes(z->data.s);
+                                        break;
+                                    default:
+                                        LM_ERR("Unsupported Final Unit Indication AVP.\n");
+                                }
+                                z = z->next;
+                            }
+                            cdp_avp->cdp->AAAFreeAVPList(&y);
+                    }
+                    mscc_avp = mscc_avp->next;
+                }
+                cdp_avp->cdp->AAAFreeAVPList(mscc_avp_list_ptr);
+                break;
+            case AVP_Result_Code:
+                x = get_4bytes(avp->data.s);
+                ro_cca_data->resultcode = x;
+                break;
+        }
+        avp = avp->next;
+    }
+    ro_cca_data->mscc = mscc;
+    return ro_cca_data;
+
+out_of_memory:
+    LM_ERR("out of pkg memory\n");
+    Ro_free_CCA(ro_cca_data);
+    return 0;
+}

+ 10 - 0
modules/ims_charging/ccr.h

@@ -0,0 +1,10 @@
+#ifndef _Client_Ro_CCR_H
+#define _Client_Ro_CCR_H
+
+#include "../cdp/diameter.h"
+#include "Ro_data.h"
+
+AAAMessage *Ro_new_ccr(AAASession * session, Ro_CCR_t *);
+Ro_CCA_t *Ro_parse_CCA_avps(AAAMessage *cca);
+
+#endif

+ 12 - 0
modules/ims_charging/config.h

@@ -0,0 +1,12 @@
+#ifndef __CLIENT_RO_CONFIG_H
+#define __CLIENT_RO_CONFIG_H
+
+typedef struct {
+    str origin_host;
+    str origin_realm;
+    str destination_realm;
+    str destination_host;
+    str * service_context_id;
+} client_ro_cfg;
+
+#endif

+ 146 - 0
modules/ims_charging/dialog.c

@@ -0,0 +1,146 @@
+#include "dialog.h"
+#include "ro_session_hash.h"
+
+struct cdp_binds cdpb;
+
+void dlg_reply(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) {
+	struct sip_msg *reply = _params->rpl;
+	struct ro_session* session = 0;
+	struct ro_session_entry* ro_session_entry;
+	time_t now = time(0);
+	time_t time_since_last_event;
+
+	LM_DBG("dlg_reply callback entered\n");
+
+//	if (reply != FAKED_REPLY && reply->REPLY_STATUS == 200) {
+//		//get CC session from callback param
+//		char* cdp_session_id = (char*)*_params->param;
+//		LM_INFO("Call answered\n");
+//		LM_DBG("Call answered and we have a session id of [%s]\n", cdp_session_id);
+//
+//		str session_id;
+//		session_id.s = cdp_session_id;
+//		session_id.len = strlen(cdp_session_id);
+//		AAASession* cdp_session = cdpb.AAAGetCCAccSession(session_id);
+//		if (!cdp_session) {
+//			LM_ERR("could not find find CC App CDP session\n");
+//			return;
+//		}
+//
+//		cdpb.AAAStartChargingCCAccSession(cdp_session);
+//		cdpb.AAASessionsUnlock(cdp_session->hash);
+//	}
+
+	if (reply != FAKED_REPLY && reply->REPLY_STATUS == 200) {
+		LM_DBG("Call answered on dlg [%p] - search for Ro Session and initialise timers.\n", dlg);
+
+		session = (struct ro_session*)*_params->param;
+		if (!session) {
+			LM_ERR("Ro Session object is NULL...... aborting\n");
+			return;
+		}
+		ro_session_entry = &(ro_session_table->entries[session->h_entry]);
+		ro_session_lock(ro_session_table, ro_session_entry);
+
+		time_since_last_event = now - session->last_event_timestamp;
+		session->start_time = session->last_event_timestamp = now;
+		session->event_type = answered;
+		session->active = 1;
+		
+		ro_session_unlock(ro_session_table, ro_session_entry);
+
+		/* check to make sure that the validity of the credit is enough for the bundle */
+		int ret = 0;
+		if (session->reserved_secs < (session->valid_for - time_since_last_event)) {
+			if (session->reserved_secs > ro_timer_buffer/*TIMEOUTBUFFER*/) {
+				ret = insert_ro_timer(&session->ro_tl, session->reserved_secs - ro_timer_buffer); //subtract 5 seconds so as to get more credit before we run out
+			} else {
+				ret = insert_ro_timer(&session->ro_tl, session->reserved_secs);
+			}
+		} else {
+			if (session->valid_for > ro_timer_buffer) {
+				ret = insert_ro_timer(&session->ro_tl, session->valid_for - ro_timer_buffer); //subtract 5 seconds so as to get more credit before we run out
+			} else {
+				ret = insert_ro_timer(&session->ro_tl, session->valid_for);
+			}
+		}
+		if (ret != 0) {
+			LM_CRIT("unable to insert timer for Ro Session [%.*s]\n", session->ro_session_id.len, session->ro_session_id.s); 
+		} else {
+			ref_ro_session(session, 1);
+		}
+				
+		AAASession* cdp_session = cdpb.AAAGetCCAccSession(session->ro_session_id);
+		if (!cdp_session) {
+			LM_ERR("could not find find CC App CDP session for session [%.*s]\n", session->ro_session_id.len, session->ro_session_id.s);
+//			ro_session_unlock(ro_session_table, ro_session_entry);
+			return;
+		}
+		
+//		ro_session_unlock(ro_session_table, ro_session_entry);
+
+		cdpb.AAAStartChargingCCAccSession(cdp_session);
+		cdpb.AAASessionsUnlock(cdp_session->hash);		
+		
+//		unref_ro_session(session, 1);	DONT need this anymore because we don't do lookup so no addition to ref counter
+	}
+}
+
+/* this may be called for a number of callbacks related to tearing down a dialog so beware. */
+void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) {
+	int i;
+	int unref = 0;
+	struct ro_session *ro_session = 0;
+	struct ro_session_entry *ro_session_entry;
+
+	LM_DBG("dialog [%p] terminated, lets send stop record\n", dlg);
+
+	if (!_params) {
+		return;
+	}
+
+	struct sip_msg *request = _params->req;
+	if (!request) {
+		LM_WARN("dlg_terminated has no SIP request associated.\n");
+	}
+
+	if (dlg && (dlg->callid.s && dlg->callid.len > 0)) {
+		/* find the session for this call, possibly both for orig and term*/
+		for (i=0; i<2; i++) {
+			//TODO: try and get the Ro session specifically for terminating dialog or originating one
+			//currently the way we are doing is a hack.....
+			if ((ro_session = lookup_ro_session(dlg->h_entry, &dlg->callid, 0, 0))) {
+				ro_session_entry =
+						&(ro_session_table->entries[ro_session->h_entry]);
+
+				//if the Ro session is not active we don't need to do anything. This prevents
+				//double processing for various dialog_terminated callback events.
+				//If however, the call was never answered, then we can continue as normal
+				if (!ro_session->active && (ro_session->start_time != 0)) {
+					unref_ro_session(ro_session,1);
+					LM_ERR("Ro Session is not active, but may have been answered [%d]\n", (int)ro_session->start_time);
+					continue;
+				}
+				
+				ro_session_lock(ro_session_table, ro_session_entry);
+				int ret = remove_ro_timer(&ro_session->ro_tl);
+				if (ret < 0) {
+					LM_CRIT("unable to unlink the timer on ro_session %p [%.*s]\n", 
+						ro_session, ro_session->cdp_session_id.len, ro_session->cdp_session_id.s);
+				} else if (ret > 0) {
+					LM_WARN("inconsistent ro timer data on ro_session %p [%.*s]\n",
+						ro_session, ro_session->cdp_session_id.len, ro_session->cdp_session_id.s);						
+				} else {
+					unref++;
+				}
+
+				LM_DBG("Sending CCR STOP on Ro_Session [%p]\n", ro_session);
+				send_ccr_stop(ro_session);
+				ro_session->active = 0;
+				//ro_session->start_time;
+				ro_session_unlock(ro_session_table, ro_session_entry);
+				unref_ro_session(ro_session, 2+unref);
+			}
+		}
+	}
+}

+ 14 - 0
modules/ims_charging/dialog.h

@@ -0,0 +1,14 @@
+#ifndef __DIALOG_H
+#define __DIALOG_H
+
+#include "../../modules/dialog_ng/dlg_load.h"
+#include "../../modules/dialog_ng/dlg_hash.h"
+#include "../cdp/cdp_load.h"
+#include "ims_ro.h"
+
+extern int ro_timer_buffer;
+
+void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params);
+void dlg_reply(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params);
+
+#endif

+ 40 - 0
modules/ims_charging/diameter_ro.c

@@ -0,0 +1,40 @@
+#include "../cdp/diameter_epc.h"
+#include "Ro_data.h"
+#include "diameter_ro.h"
+
+int AAASendCCR(AAASession *session) {
+
+    return 1;
+}
+
+/**
+ * Handler for incoming Diameter requests.
+ * @param request - the received request
+ * @param param - generic pointer
+ * @returns the answer to this request
+ */
+void RoChargingResponseHandler(AAAMessage *response, void *param) {
+    switch (response->applicationId) {
+        case IMS_Ro:
+            switch (response->commandCode) {
+                case Diameter_CCA:
+                    break;
+                default:
+                    LM_ERR("ERR:"M_NAME":RoChargingResponseHandler: - "
+                            "Received unknown response for Ro command %d, flags %#1x endtoend %u hopbyhop %u\n",
+                            response->commandCode, response->flags,
+                            response->endtoendId, response->hopbyhopId);
+                    return;
+            }
+            break;
+        default:
+            LM_ERR("DBG:"M_NAME":RoChargingResponseHandler(): - Received unknown response for app %d command %d\n",
+                    response->applicationId,
+                    response->commandCode);
+            LM_ERR("Reponse is [%s]", response->buf.s);
+            return;
+
+    }
+    return;
+}
+

+ 11 - 0
modules/ims_charging/diameter_ro.h

@@ -0,0 +1,11 @@
+#ifndef __CLIENT_RF_DIAMETER_RO_H
+#define __CLIENT_RF_DIAMETER_RO_H
+
+#include "../cdp/cdp_load.h"
+#include "../cdp_avp/mod_export.h"
+
+int AAASendCCR(AAASession * session);
+
+void RoChargingResponseHandler(AAAMessage *response, void *param);
+
+#endif /* __CLIENT_RF_DIAMETER_RO_H */

+ 4 - 0
modules/ims_charging/doc/Makefile

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

BIN
modules/ims_charging/doc/images/charging1.png


BIN
modules/ims_charging/doc/images/charging2.png


+ 50 - 0
modules/ims_charging/doc/ims_charging.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"
+[
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+]>
+<book>
+  <bookinfo>
+    <title>The IMS Charging Module</title>
+    <authorgroup>
+      <author>
+        <firstname>Jason</firstname>
+        <surname>Penton</surname>
+        <affiliation>
+          <orgname>Smile Communications</orgname>
+        </affiliation>
+        <email>[email protected]</email>
+      </author>
+      <author>
+        <firstname>Carsten</firstname>
+        <surname>Bock</surname>
+        <affiliation>
+          <orgname>ng-voice GmbH</orgname>
+        </affiliation>
+        <email>[email protected]</email>
+      </author>
+      <author>
+        <firstname>Carlos</firstname>
+        <surname>Ruiz Diaz</surname>
+        <affiliation>
+          <orgname>ng-voice GmbH</orgname>
+        </affiliation>
+        <email>[email protected]</email>
+      </author>
+    </authorgroup>
+    <copyright>
+      <year>2013</year>
+      <holder>Smile Communications</holder>
+    </copyright>
+    <copyright>
+      <year>2013</year>
+      <holder>ng-voice GmbH</holder>
+    </copyright>
+  </bookinfo>
+  <toc />
+  <xi:include href="ims_charging_admin.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+</book>
+

+ 572 - 0
modules/ims_charging/doc/ims_charging_admin.xml

@@ -0,0 +1,572 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module Admin Guide -->
+<chapter>
+  <title>Admin Guide</title>
+  <section>
+    <title>Overview</title>
+    <para>This module contains all methods related to the IMS charging control functions performed
+    by an network element (e.g. a S-CSCF) over the Ro interface. 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>
+  </section>
+  <section>
+    <title>Dependencies</title>
+    <section>
+      <title>Kamailio Modules</title>
+      <para>The Following mouldes must be loaded before this module:</para>
+      <itemizedlist>
+        <listitem>
+          <para>Dialog_ng</para>
+        </listitem>
+        <listitem>
+          <para>TM - Transaction Manager</para>
+        </listitem>
+        <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>This modules requires the internal IMS library.</para>
+    </section>
+  </section>
+  <section>
+    <title>Understanding Charging in the IP-Multimedia-Subsystem (IMS)</title>
+    <para>Before each service usage, the charging system must be asked for permission (credit
+    authorization). The charging server must make a decision: Either authorize or deny the session.
+    For postpaid scenarios this is fairly easy: The charging-server only needs to collect the usage
+    data for processing it at the end of the month. As no realtime account updating is needed, this
+    is often called "offline-charging". For prepaid scenarios the charging server needs to know the
+    user's account balance and it will need to update the account in real-time. This is often
+    referred to as "online-charging".</para>
+    <para>Question: What is the double of the Radius? Answer: It's the Diameter!</para>
+    <para>As quite often, we use the Diameter-Protocol to do the Charging in the IMS. And as quite
+    often, IMS uses a huge bunch of acronyms to describe the different interfaces: We call the
+    diameter-interface for offline-charging the "Rf"-interface and the interface for online
+    charging the "Ro"-interface.</para>
+    <para>Each system, that needs this credit authorization, have to be equipped with a proper
+    charging trigger, a so-called charging-trigger-function (CTF) in order to communicate with the
+    charging-server (also called charging-function):</para>
+    <mediaobject>
+      <imageobject>
+        <imagedata fileref="./images/charging1.png" />
+      </imageobject>
+    </mediaobject>
+    <section>
+      <title>Offline Charging (Rf)</title>
+      <para>For the offlinc charging (Rf), we have the following two diameter-messages:</para>
+      <itemizedlist>
+        <listitem>
+          <para>ACR - Accounting Request</para>
+        </listitem>
+        <listitem>
+          <para>ACA - Accounting Answer</para>
+        </listitem>
+      </itemizedlist>
+      <para>Each request can have the following Accounting-Record-Type:</para>
+      <itemizedlist>
+        <listitem>
+          <para>START_RECORD - used to start an accounting session, typically when the application
+          receives a SIP 200 OK acknowledging an initial SIP INVITE.</para>
+        </listitem>
+        <listitem>
+          <para>INTERIM_RECORD - used to update a session, for example, in the case of SIP
+          RE-INVITE and/or UPDATE in the current SIP dialog.</para>
+        </listitem>
+        <listitem>
+          <para>STOP_RECORD - used to stop an accounting session, for example, when the application
+          receives a SIP BYE message.</para>
+        </listitem>
+        <listitem>
+          <para>EVENT_RECORD - used for event-based accounting, e.g. a short message or
+          similar</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+    <section>
+      <title>Online Charging (Ro)</title>
+      <para>For online charging (Ro), this get's a little bit more complicated. The charging
+      function needs to perform credit control before allowing resource usage. The prepaid
+      subscriber needs to exist in the charging-server and all activities must be monitored by the
+      charging-server. We must distinguish between the following two cases:</para>
+      <itemizedlist>
+        <listitem>
+          <para>Direct debiting - the amount is immediately deducted from the user's account in one
+          single transaction. This could be for example a SMS or the ordering of a movie in case of
+          Video-on-Demand.</para>
+        </listitem>
+        <listitem>
+          <para>Unit reservation - an amount is reserved by the charging-server. This is done,
+          because the charging-server does not know yet, how many units are needed to provide the
+          service. During the session, the used amount may be deducted and more units can be
+          requested; at the end of the session the used sessions are reported in the final request.
+          These sessions could be typically a voice- or video-call or a Pay-TV session, if you pay
+          per usage.</para>
+        </listitem>
+      </itemizedlist>
+      <para>As a result, we have the following three scenarios:</para>
+      <itemizedlist>
+        <listitem>
+          <para>Immediate Event Charging (IEC) - used for simple Event-based charging</para>
+        </listitem>
+        <listitem>
+          <para>Event Charging with Unit Reservation (ECUR) (of type Event-based charging)</para>
+        </listitem>
+        <listitem>
+          <para>Session Charging with Unit Reservation (SCUR) (of type Session-based
+          charging)</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+    <section>
+      <title>Online Charging (Ro): A practical example</title>
+      <para>But how does it look in reality? Let us make a more practical example:</para>
+      <para>Let us assume we have a subscriber, who has sufficient credit for 75 seconds of
+      talking. The subscriber initiates a call; as we do not know, how long the call will take, we
+      start with requesting credit for 30 seconds (CCR-Request, we could request any duration, e.g.
+      2 hours, but it would probably block other calls if we reserve all the required
+      credit).</para>
+      <para>The call proceeds, so after 30 seconds we send another CCR-Request with the indication
+      that we used the reserved 30 seconds and that we request another 30 seconds. We reduce the
+      account of the subscriber by 30 seconds, so he has a credit of 45 seconds. Since 45 seconds
+      is more than the requested 30 seconds, this second request can also easily be accepted and
+      another 30 seconds can be granted. After this request, the account is at 45 seconds and we
+      still (or again) have 30 seconds reserved.</para>
+      <para>Meanwhile the subscriber initiates a second call. We try to request again 30 seconds
+      from the charging-server, but as our account is at 45 seconds of speaking time and since we
+      reserved another 30 seconds for the first call, we can only grant 15 seconds for the second
+      call. The last 15 seconds are now reserved for this subscriber; we have 45 seconds on the
+      account of which 45 seconds are reserved.</para>
+      <para>Now the first call gets terminated: We only used 20 seconds from the granted 30
+      seconds. So we decrease the account of the subscriber by 20 seconds and we reduce the amount
+      of reserved units by 30. We have 25 seconds in the account and we have still reserved 15
+      seconds for the second call.</para>
+      <para>As the second call is still proceeding, we will try to request another 30 seconds and
+      we indicate, that we used the granted 15 seconds. The account is deducted by 15 seconds (the
+      used units) and we can grant another 10 seconds for the second call, as this is the remains
+      on the account.</para>
+      <para>After 10 seconds, no more units can be granted, so the call is teared down.</para>
+      <para>The following diagram is a graphical representation of the above example:</para>
+      <mediaobject>
+        <imageobject>
+          <imagedata fileref="./images/charging2.png" />
+        </imageobject>
+      </mediaobject>
+    </section>
+  </section>
+  <section>
+    <title>Parameters</title>
+    <section>
+      <title>
+      <varname>hash_size</varname>(int)</title>
+      <para>The size of the hash table internally used to keep the Diameter-Ro-Session. A larger
+      table is much faster but consumes more memory. The hash size must be a power of two
+      number.</para>
+      <para>IMPORTANT: If Ro-Session's information should be stored in a database, a constant
+      hash_size should be used, otherwise the restoring process will not take place. If you really
+      want to modify the hash_size you must delete all table's rows before restarting the
+      server.</para>
+      <para>
+        <emphasis>Default value is 4096.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>hash_size</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "hash_size", 1024)
+        ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>interim_update_credits</varname>(int)</title>
+      <para>How much credit should be requested interim request? At the start of the call, we
+      request the amout of seconds as per Command. For each interim request, we would request
+      credit for "interim_update_credits".</para>
+      <para>
+        <emphasis>Default value is 30.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>interim_update_credits</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "interim_update_credits", 600) ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>timer_buffer</varname>(int)</title>
+      <para>How many seconds before expiry of our credit should we request more credit?</para>
+      <para>
+        <emphasis>Default value is 8.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>timer_buffer</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "timer_buffer", 10)
+        ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>ro_forced_peer</varname>(string)</title>
+      <para>This is the optional name of the origin host of the Diameter server (typically a
+      Charging Server). If not set then realm routing is used.</para>
+      <para>
+        <emphasis>Default value is ''.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>ro_forced_peer</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "ro_forced_peer",
+        "ocs.ims.smilecoms.com") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>ro_auth_expiry</varname>(integer)</title>
+      <para>This is the expiry length in seconds of the initiated Diameter sessions.</para>
+      <para>
+        <emphasis>Default value is 7200.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>ro_auth_expiry</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "ro_auth_expiry", 14400)
+        ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>ro_auth_expiry</varname>(integer)</title>
+      <para>This is the expiry length in seconds of the initiated Diameter sessions.</para>
+      <para>
+        <emphasis>Default value is 7200.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>ro_auth_expiry</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "ro_auth_expiry", 14400)
+        ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>cdp_event_latency</varname>(integer)</title>
+      <para>This is a flag to determine whether or slow CDP responses should be reported in the log
+      file. 1 is enabled and 0 is disabled.</para>
+      <para>
+        <emphasis>Default value is 1.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>cdp_event_latency</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "cdp_event_latency", 1)
+        ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>cdp_event_threshold</varname>(integer)</title>
+      <para>This time in milliseconds is the limit we should report a CDP response as slow. i.e. if
+      a CDP response exceeds this limit it will be reported in the log file. This is only relevant
+      is cdp_event_latency is enabled (set to 0).</para>
+      <para>
+        <emphasis>Default value is 500.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>cdp_event_threshold</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "cdp_event_threshold",
+        500) ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>cdp_event_latency_log</varname>(integer)</title>
+      <para>This time log level at which we should report slow CDP responses. 0 is ERROR, 1 is
+      WARN, 2 is INFO and 3 is DEBUG. This is only relevant is cdp_event_latency is enabled (set to
+      0)</para>
+      <para>
+        <emphasis>Default value is 0.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>cdp_event_latency_log</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "cdp_event_latency_log",
+        1) ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>origin_host</varname>(string)</title>
+      <para>Origin host to be used in Diameter messages to charging-server.</para>
+      <para>
+        <emphasis>Default value is "scscf.ims.smilecoms.com".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>origin_host</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "origin_host",
+        "scscf.kamailio-ims.org") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>origin_realm</varname>(string)</title>
+      <para>Origin Realm to be used in Diameter messages to charging-server.</para>
+      <para>
+        <emphasis>Default value is "ims.smilecome.com".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>origin_realm</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "origin_realm",
+        "kamailio-ims.org") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>destination_host</varname>(string)</title>
+      <para>Destination host to be used in Diameter messages to charging-server.</para>
+      <para>
+        <emphasis>Default value is 5s.</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>destination_host</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "destination_host",
+        "ocs.kamailio-ims.org") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>destination_realm</varname>(string)</title>
+      <para>Destination realm to be used in Diameter messages to charging-server.</para>
+      <para>
+        <emphasis>Default value is "ims.smilecoms.com".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>destination_realm</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging", "destination_realm",
+        "kamailio-ims.org") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>service_context_id_root</varname>(string)</title>
+      <para>This defines a root-element of the Service-Context-Id AVP used in the
+      diameter-message</para>
+      <para>The Service-Context-Id AVP is of type UTF8String (AVP Code 461) and contains a unique
+      identifier of the Diameter credit-control service specific document that applies to the
+      request (as defined in section RFC 4006 4.1.2). This is an identifier allocated by the
+      service provider, by the service element manufacturer, or by a standardization body, and MUST
+      uniquely identify a given Diameter credit-control service specific document. The format of
+      the Service-Context-Id is:</para>
+      <programlisting format="linespecific">"service-context" "@" "domain" service-context =
+      Token</programlisting>
+      <para>The Token is an arbitrary string of characters and digits.</para>
+      <para>'domain' represents the entity that allocated the Service-Context-Id. It can be
+      ietf.org, 3gpp.org, etc., if the identifier is allocated by a standardization body, or it can
+      be the FQDN of the service provider (e.g., provider.example.com) or of the vendor (e.g.,
+      vendor.example.com) if the identifier is allocated by a private entity.</para>
+      <para>Service-specific documents that are for private use only (i.e., to one provider's own
+      use, where no interoperability is deemed useful) may define private identifiers without need
+      of coordination. However, when interoperability is wanted, coordination of the identifiers
+      via, for example, publication of an informational RFC is RECOMMENDED in order to make
+      Service-Context-Id globally available.</para>
+      <para>
+        <emphasis>Default value is "[email protected]".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>service_context_id_root</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "service_context_id_root", "[email protected]") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>service_context_id_ext</varname>(string)</title>
+      <para>This defines the extension of the Service-Context-Id AVP used in the
+      diameter-message.</para>
+      <para>
+        <emphasis>Default value is "ext".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>service_context_id_ext</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "service_context_id_ext", "ext2") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>service_context_id_mnc</varname>(string)</title>
+      <para>This defines Mobile-Network-Code (MNC) of the Service-Context-Id AVP used in the
+      diameter-message.</para>
+      <para>
+        <emphasis>Default value is "01".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>service_context_id_mnc</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "service_context_id_mnc", "42") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>service_context_id_mcc</varname>(string)</title>
+      <para>This defines Mobile-Country-Code (MCC) of the Service-Context-Id AVP used in the
+      diameter-message.</para>
+      <para>see https://en.wikipedia.org/wiki/Mobile_country_code_(MCC) for details.</para>
+      <para>
+        <emphasis>Default value is "001".</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>service_context_id_mcc</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "service_context_id_mcc", "262") ...</programlisting>
+      </example>
+    </section>
+    <section>
+      <title>
+      <varname>service_context_id_release</varname>(string)</title>
+      <para>This defines Release of the Service-Context-Id AVP used in the diameter-message.</para>
+      <para>
+        <emphasis>Default value is "8" (Release 8).</emphasis>
+      </para>
+      <example>
+        <title>
+        <varname>service_context_id_release</varname>parameter usage</title>
+        <programlisting format="linespecific">... modparam("ims_charging",
+        "service_context_id_release", "262") ...</programlisting>
+      </example>
+    </section>
+  </section>
+  <section>
+    <title>Functions</title>
+    <section>
+      <title>
+        <function moreinfo="none">Ro_CCR(route_name, direction, charge_type, unit_type,
+        reservation_units)</function>
+      </title>
+      <para>Perform a CCR on Diameter Ro interface for Charging</para>
+      <para>Meaning of the parameters is as follows:</para>
+      <itemizedlist>
+        <listitem>
+          <para>
+          <emphasis>route_name</emphasis>route to be executed upon reception of charging
+          requests</para>
+        </listitem>
+        <listitem>
+          <para>
+          <emphasis>direction</emphasis>"orig"inating or "term"inating</para>
+        </listitem>
+        <listitem>
+          <para>
+          <emphasis>charge_type</emphasis>"IEC" = Immediate Event Charging, "ECUR" - Event Charging
+          with Unit Reservation or "SCUR" - Session Charging with Unit Reservation.</para>
+          <emphasis>(Note: At the moment only SCUR is supported)</emphasis>
+        </listitem>
+        <listitem>
+          <para>
+          <emphasis>unit_type</emphasis>Types of the unit to be requested</para>
+          <emphasis>(unused at the moment)</emphasis>
+        </listitem>
+        <listitem>
+          <para>
+          <emphasis>reservation_units</emphasis>how many units (at the moment seconds) should be
+          reservated at the moment.</para>
+        </listitem>
+      </itemizedlist>
+      <para>This function can be used from REQUEST_ROUTE.</para>
+      <para>This method is executed asynchronously. See example on how to retrieve return
+      value.</para>
+      <example>
+        <title>Ro_CCR</title>
+        <programlisting format="linespecific">... xlog("L_DBG","Sending initial CCR Request for
+        call\n"); Ro_CCR("CHARGING_CCR_REPLY", "orig", "SCUR", "", "30"); }
+        route[CHARGING_CCR_REPLY] { xlog("L_DBG","cca_return code is $avp(s:cca_return_code)\n");
+        switch ($avp(s:cca_return_code)){ case 1: #success xlog("L_DBG", "CCR success - will route
+        message\n"); route(Finalize_Orig); break; case -1: #failure xlog("L_ERR", "CCR failure -
+        error response sent from module\n"); sl_send_reply("402","Payment required"); break; case
+        -2: #error xlog("L_ERR", "CCR error - error response sent from module\n");
+        sl_send_reply("500", "Charging Error"); break; default: xlog("L_ERR", "Unknown return code
+        from CCR: [$avp(s:cca_return_code)] \n"); break; } exit; } ...</programlisting>
+      </example>
+    </section>
+  </section>
+  <section>
+    <title>Statistics</title>
+    <section>
+      <title>Initial CCRs (initial_ccrs)</title>
+      <para>The number of initial CCRs, i.e., the CCRs that were sent for the initial INVITEs.</para>
+    </section>
+    <section>
+      <title>Interim CCRs (interim_ccrs)</title>
+      <para>The number of CCRs sent within established sessions.</para>
+    </section>
+    <section>
+      <title>Final CCRs (final_ccrs)</title>
+      <para>The number of CCRs sent to terminate a session.</para>
+    </section>
+    <section>
+      <title>Sucessful initial CCRs (successful_initial_ccrs)</title>
+      <para>Initial CCRs that ended with DIAMETER_SUCCESS response code.</para>
+    </section>
+    <section>
+      <title>Sucessful interim CCRs (successful_interim_ccrs)</title>
+      <para>Interim CCRs that ended with DIAMETER_SUCCESS response code.</para>
+    </section>
+    <section>
+      <title>Sucessful final CCRs (successful_final_ccrs)</title>
+      <para>Final CCRs that ended with DIAMETER_SUCCESS response code.</para>
+    </section>
+    <section>
+      <title>Failed initial CCRs (failed_initial_ccrs)</title>
+      <para>Initial CCRs that ended with no DIAMETER_SUCCESS response or with some other error during processing.</para>
+    </section>
+    <section>
+      <title>Failed interim CCRs (failed_interim_ccrs)</title>
+      <para>Interim CCRs that ended with no DIAMETER_SUCCESS response or with some other error during processing.</para>
+    </section>
+    <section>
+      <title>Failed final CCRs (failed_final_ccrs)</title>
+      <para>Final CCRs that ended with no DIAMETER_SUCCESS response or with some other error during processing.</para>
+    </section>
+    <section>
+      <title>CCRs average response time (ccr_avg_response_time)</title>
+      <para>Average CCA arrival time in milliseconds.</para>
+    </section>
+    <section>
+      <title>CCRs responses time (ccr_responses_time)</title>
+      <para>Total CCA arrival time in milliseconds.</para>
+    </section>
+    <section>
+      <title>Billed seconds (billed_secs)</title>
+      <para>Number of seconds billed in total.</para>
+    </section>
+    <section>
+      <title>Killed calls (killed_calls)</title>
+      <para>Number of calls that were killed due to lack of credit.</para>
+    </section>
+
+  </section>
+</chapter>
+

+ 1164 - 0
modules/ims_charging/ims_ro.c

@@ -0,0 +1,1164 @@
+#include "mod.h"
+
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_uri.h"
+#include "../../sr_module.h"
+#include "../../socket_info.h"
+#include "../../timer.h"
+#include "../../locking.h"
+#include "../../modules/tm/tm_load.h"
+
+#include "../../modules/dialog_ng/dlg_hash.h"
+#include "../../modules/dialog_ng/dlg_load.h"
+
+
+#include "../cdp/cdp_load.h"
+#include "../../mod_fix.h"
+
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_to.h"
+
+#include "../../lib/ims/ims_getters.h"
+
+#include "diameter_ro.h"
+#include "ims_ro.h"
+#include "Ro_data.h"
+#include "dialog.h"
+
+#include "ccr.h"
+#include "config.h"
+#include "ro_session_hash.h"
+#include "stats.h"
+
+extern struct tm_binds tmb;
+extern struct cdp_binds cdpb;
+extern client_ro_cfg cfg;
+extern struct dlg_binds dlgb;
+extern cdp_avp_bind_t *cdp_avp;
+
+struct session_setup_data {
+	struct ro_session *ro_session;
+
+	cfg_action_t* action;
+	unsigned int tindex;
+	unsigned int tlabel;
+};
+
+struct dlg_binds* dlgb_p;
+extern struct tm_binds tmb;
+
+int interim_request_credits;
+
+static int create_cca_return_code(int result);
+static void resume_on_initial_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs);
+static void resume_on_interim_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs);
+static void resume_on_termination_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs);
+
+void credit_control_session_callback(int event, void* session) {
+	switch (event) {
+		case AUTH_EV_SESSION_DROP:
+			LM_DBG("Received notification of CC App session drop - we must free the generic data\n");
+			break;
+		default:
+			LM_DBG("Received unhandled event [%d] in credit control session callback from CDP\n", event);
+	}
+}
+
+int get_direction_as_int(str* direction);
+
+/**
+ * Retrieves the SIP request that generated a diameter transaction
+ * @param hash - the tm hash value for this request
+ * @param label - the tm label value for this request
+ * @returns the SIP request
+ */
+struct sip_msg * trans_get_request_from_current_reply() {
+    struct cell *t;
+    t = tmb.t_gett();
+    if (!t || t == (void*) - 1) {
+        LM_ERR("trans_get_request_from_current_reply: Reply without transaction\n");
+        return 0;
+    }
+    if (t) return t->uas.request;
+    else return 0;
+}
+
+/**
+ * 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
+ */
+static inline int Ro_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
+ */
+static inline int Ro_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;
+}
+
+/**
+ * Creates and adds a Destination-Realm AVP.
+ * @param msg - the Diameter message to add to.
+ * @param data - the value for the AVP payload
+ * @returns 1 on success or 0 on error
+ */
+inline int ro_add_destination_realm_avp(AAAMessage *msg, str data) {
+    return
+    Ro_add_avp(msg, data.s, data.len,
+            AVP_Destination_Realm,
+            AAA_AVP_FLAG_MANDATORY,
+            0,
+            AVP_DUPLICATE_DATA,
+            __FUNCTION__);
+}
+
+inline int Ro_add_cc_request(AAAMessage *msg, unsigned int cc_request_type, unsigned int cc_request_number) {
+    char x[4];
+    set_4bytes(x, cc_request_type);
+    int success = Ro_add_avp(msg, x, 4, AVP_CC_Request_Type, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    char y[4];
+    set_4bytes(y, cc_request_number);
+
+    return success && Ro_add_avp(msg, y, 4, AVP_CC_Request_Number, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+}
+
+inline int Ro_add_event_timestamp(AAAMessage *msg, time_t now) {
+    char x[4];
+    str s = {x, 4};
+    uint32_t ntime = htonl(now + EPOCH_UNIX_TO_EPOCH_NTP);
+    memcpy(x, &ntime, sizeof (uint32_t));
+
+    return Ro_add_avp(msg, s.s, s.len, AVP_Event_Timestamp, AAA_AVP_FLAG_NONE, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+}
+
+inline int Ro_add_user_equipment_info(AAAMessage *msg, unsigned int type, str value) {
+    AAA_AVP_LIST list;
+    str group;
+    char x[4];
+
+    list.head = 0;
+    list.tail = 0;
+
+    set_4bytes(x, type);
+    Ro_add_avp_list(&list, x, 4, AVP_User_Equipment_Info_Type, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    Ro_add_avp_list(&list, value.s, value.len, AVP_User_Equipment_Info_Value, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    group = cdpb.AAAGroupAVPS(list);
+
+    cdpb.AAAFreeAVPList(&list);
+
+    return Ro_add_avp(msg, group.s, group.len, AVP_User_Equipment_Info, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+}
+
+inline int Ro_add_termination_casue(AAAMessage *msg, unsigned int term_code) {
+    char x[4];
+    str s = {x, 4};
+    uint32_t code = htonl(term_code);
+    memcpy(x, &code, sizeof (uint32_t));
+
+    return Ro_add_avp(msg, s.s, s.len, AVP_Termination_Cause, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+}
+
+/* called only when building stop record AVPS */
+inline int Ro_add_multiple_service_credit_Control_stop(AAAMessage *msg, int used_unit) {
+    AAA_AVP_LIST used_list, mscc_list;
+    str used_group;
+    char x[4];
+
+    unsigned int service_id = 1000; //VOICE TODO FIX as config item
+
+    used_list.head = 0;
+    used_list.tail = 0;
+    mscc_list.head = 0;
+    mscc_list.tail = 0;
+
+    /* if we must Used-Service-Unit */
+    if (used_unit >= 0) {
+        set_4bytes(x, used_unit);
+        Ro_add_avp_list(&used_list, x, 4, AVP_CC_Time, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+        used_group = cdpb.AAAGroupAVPS(used_list);
+        cdpb.AAAFreeAVPList(&used_list);
+        Ro_add_avp_list(&mscc_list, used_group.s, used_group.len, AVP_Used_Service_Unit, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+    }
+
+    set_4bytes(x, service_id);
+    Ro_add_avp_list(&mscc_list, x, 4, AVP_Service_Identifier, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    used_group = cdpb.AAAGroupAVPS(mscc_list);
+    cdpb.AAAFreeAVPList(&mscc_list);
+
+    return Ro_add_avp(msg, used_group.s, used_group.len, AVP_Multiple_Services_Credit_Control, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+}
+
+inline int Ro_add_multiple_service_credit_Control(AAAMessage *msg, unsigned int requested_unit, int used_unit) {
+    AAA_AVP_LIST list, used_list, mscc_list;
+    str group, used_group;
+    unsigned int service_id = 1000; //VOICE TODO FIX as config item - should be a MAP that can be identified based on SDP params
+    char x[4];
+
+    list.head = 0;
+    list.tail = 0;
+    used_list.head = 0;
+    used_list.tail = 0;
+    mscc_list.head = 0;
+    mscc_list.tail = 0;
+
+    set_4bytes(x, requested_unit);
+    Ro_add_avp_list(&list, x, 4, AVP_CC_Time, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+    group = cdpb.AAAGroupAVPS(list);
+    cdpb.AAAFreeAVPList(&list);
+
+    Ro_add_avp_list(&mscc_list, group.s, group.len, AVP_Requested_Service_Unit, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+
+    set_4bytes(x, service_id);
+    Ro_add_avp_list(&mscc_list, x, 4, AVP_Service_Identifier, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    /* if we must Used-Service-Unit */
+    if (used_unit >= 0) {
+        set_4bytes(x, used_unit);
+        Ro_add_avp_list(&used_list, x, 4, AVP_CC_Time, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+        used_group = cdpb.AAAGroupAVPS(used_list);
+        cdpb.AAAFreeAVPList(&used_list);
+        Ro_add_avp_list(&mscc_list, used_group.s, used_group.len, AVP_Used_Service_Unit, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+    }
+
+    group = cdpb.AAAGroupAVPS(mscc_list);
+    cdpb.AAAFreeAVPList(&mscc_list);
+
+    return Ro_add_avp(msg, group.s, group.len, AVP_Multiple_Services_Credit_Control, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+}
+
+inline int Ro_add_subscription_id(AAAMessage *msg, unsigned int type, str *subscription_id)//, struct sip_msg* sip_msg)
+{
+    AAA_AVP_LIST list;
+    str group;
+    char x[4];
+
+    list.head = 0;
+    list.tail = 0;
+
+    set_4bytes(x, type);
+    Ro_add_avp_list(&list, x, 4, AVP_Subscription_Id_Type, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    Ro_add_avp_list(&list, subscription_id->s, subscription_id->len, AVP_Subscription_Id_Data, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    group = cdpb.AAAGroupAVPS(list);
+
+    cdpb.AAAFreeAVPList(&list);
+
+    return Ro_add_avp(msg, group.s, group.len, AVP_Subscription_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+}
+
+/**
+ * Creates and adds a Vendor-Specifig-Application-ID AVP.
+ * @param msg - the Diameter message to add to.
+ * @param vendor_id - the value of the vendor_id,
+ * @param auth_id - the authorization application id
+ * @param acct_id - the accounting application id
+ * @returns 1 on success or 0 on error
+ */
+inline int Ro_add_vendor_specific_appid(AAAMessage *msg, unsigned int vendor_id, unsigned int auth_id, unsigned int acct_id) {
+    AAA_AVP_LIST list;
+    str group;
+    char x[4];
+
+    list.head = 0;
+    list.tail = 0;
+
+    set_4bytes(x, vendor_id);
+    Ro_add_avp_list(&list, x, 4, AVP_Vendor_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+
+    if (auth_id) {
+        set_4bytes(x, auth_id);
+        Ro_add_avp_list(&list, x, 4, AVP_Auth_Application_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+    }
+    if (acct_id) {
+        set_4bytes(x, acct_id);
+        Ro_add_avp_list(&list, x, 4, AVP_Acct_Application_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);
+    }
+
+    group = cdpb.AAAGroupAVPS(list);
+
+    cdpb.AAAFreeAVPList(&list);
+
+    return Ro_add_avp(msg, group.s, group.len, AVP_Vendor_Specific_Application_Id, AAA_AVP_FLAG_MANDATORY, 0, AVP_FREE_DATA, __FUNCTION__);
+}
+
+int get_sip_header_info(struct sip_msg * req,
+        struct sip_msg * reply,
+        int32_t * acc_record_type,
+        str * sip_method,
+        str * event, uint32_t * expires,
+        str * callid, str * asserted_id_uri, str * to_uri) {
+
+    sip_method->s = req->first_line.u.request.method.s;
+    sip_method->len = req->first_line.u.request.method.len;
+
+    if (strncmp(sip_method->s, "INVITE", 6) == 0)
+        *acc_record_type = AAA_ACCT_START;
+    else if (strncmp(sip_method->s, "BYE", 3) == 0)
+        *acc_record_type = AAA_ACCT_STOP;
+    else
+        *acc_record_type = AAA_ACCT_EVENT;
+
+    *event = cscf_get_event(req);
+    *expires = cscf_get_expires_hdr(req, 0);
+    *callid = cscf_get_call_id(req, NULL);
+
+    if ((*asserted_id_uri = cscf_get_asserted_identity(req)).len == 0) {
+    	LM_DBG("No P-Asserted-Identity hdr found. Using From hdr");
+
+    	if (!cscf_get_from_uri(req, asserted_id_uri)) {
+    		LM_ERR("Error assigning P-Asserted-Identity using From hdr");
+    		goto error;
+    	}
+    }
+
+    *to_uri	= req->first_line.u.request.uri;
+
+    LM_DBG("retrieved sip info : sip_method %.*s acc_record_type %i, event %.*s expires %u "
+            "call_id %.*s from_uri %.*s to_uri %.*s\n",
+            sip_method->len, sip_method->s, *acc_record_type, event->len, event->s, *expires,
+            callid->len, callid->s, asserted_id_uri->len, asserted_id_uri->s, to_uri->len, to_uri->s);
+
+    return 1;
+error:
+    return 0;
+}
+
+int get_ims_charging_info(struct sip_msg *req, struct sip_msg * reply, str * icid, str * orig_ioi, str * term_ioi) {
+
+    LM_DBG("get ims charging info\n");
+    if (req)
+        cscf_get_p_charging_vector(req, icid, orig_ioi, term_ioi);
+    if (reply)
+        cscf_get_p_charging_vector(reply, icid, orig_ioi, term_ioi);
+
+    return 1;
+}
+
+int get_timestamps(struct sip_msg * req, struct sip_msg * reply, time_t * req_timestamp, time_t * reply_timestamp) {
+
+    if (reply)
+        *reply_timestamp = time(NULL);
+    if (req)
+        *req_timestamp = time(NULL);
+    return 1;
+}
+
+/*
+ * creates the ro session for a session establishment
+ *
+ */
+
+Ro_CCR_t * dlg_create_ro_session(struct sip_msg * req, struct sip_msg * reply, AAASession ** authp, int dir) {
+
+    Ro_CCR_t * ro_ccr_data = 0;
+    AAASession * auth = NULL;
+    str user_name/* ={0,0}*/, sip_method = {0, 0}, event = {0, 0};
+    uint32_t expires = 0;
+    str callid = {0, 0}, to_uri = {0, 0}, from_uri = {0, 0},
+    icid = {0, 0}, orig_ioi = {0, 0}, term_ioi = {0, 0};
+
+    event_type_t * event_type = 0;
+    ims_information_t * ims_info = 0;
+    time_stamps_t * time_stamps = 0;
+    time_t req_timestamp = 0, reply_timestamp = 0;
+    int32_t acc_record_type;
+    subscription_id_t subscr;
+
+    *authp = 0;
+
+    if (!get_sip_header_info(req, reply, &acc_record_type, &sip_method, &event, &expires, &callid, &from_uri, &to_uri))
+        goto error;
+    if (dir == RO_ORIG_DIRECTION) {
+        user_name.s = from_uri.s;
+        user_name.len = from_uri.len;
+    } else if (dir == RO_TERM_DIRECTION){
+        user_name.s = to_uri.s;
+        user_name.len = to_uri.len;
+    } else {
+    	LM_CRIT("don't know what to do in unknown mode - should we even get here\n");
+    	goto error;
+    }
+
+    /*	if(!get_ims_charging_info(req, reply, &icid, &orig_ioi, &term_ioi))
+                    goto error;
+     */
+    LM_DBG("retrieved ims charging info icid:[%.*s] orig_ioi:[%.*s] term_ioi:[%.*s]\n",
+    		icid.len, icid.s, orig_ioi.len, orig_ioi.s, term_ioi.len, term_ioi.s);
+
+    if (!get_timestamps(req, reply, &req_timestamp, &reply_timestamp))
+        goto error;
+
+    if (!(event_type = new_event_type(&sip_method, &event, &expires)))
+        goto error;
+
+    if (!(time_stamps = new_time_stamps(&req_timestamp, NULL, &reply_timestamp, NULL)))
+        goto error;
+
+    if (!(ims_info = new_ims_information(event_type, time_stamps, &callid, &callid, &from_uri, &to_uri, &icid, &orig_ioi, &term_ioi, dir)))
+        goto error;
+    event_type = 0;
+    time_stamps = 0;
+
+    subscr.type = Subscription_Type_IMPU;
+    subscr.id.s = from_uri.s;
+    subscr.id.len = from_uri.len;
+
+    ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr);
+    if (!ro_ccr_data) {
+        LM_ERR("dlg_create_ro_session: no memory left for generic\n");
+        goto out_of_memory;
+    }
+    ims_info = 0;
+
+    if (strncmp(req->first_line.u.request.method.s, "INVITE", 6) == 0) {
+    	//create CDP CC Accounting session
+    	auth = cdpb.AAACreateCCAccSession(credit_control_session_callback, 1/*is_session*/, NULL ); //must unlock session hash when done
+    	LM_DBG("Created Ro Session with id Session ID [%.*s]\n", auth->id.len, auth->id.s);
+        //save_session = auth->id;
+
+    }
+    /*if (strncmp(req->first_line.u.request.method.s, "BYE", 3) == 0) {
+        auth = cdp_avp->cdp->AAAGetAuthSession(save_session);
+    }*/
+
+
+    if (!auth) {
+        LM_ERR("unable to create the Ro Session\n");
+        goto error;
+    }
+
+    *authp = auth;
+    return ro_ccr_data;
+
+out_of_memory:
+error :
+    time_stamps_free(time_stamps);
+    event_type_free(event_type);
+    ims_information_free(ims_info);
+    Ro_free_CCR(ro_ccr_data);
+
+    return NULL;
+}
+
+int sip_create_ro_ccr_data(struct sip_msg * msg, int dir, Ro_CCR_t ** ro_ccr_data, AAASession ** auth) {
+
+    if (msg->first_line.type == SIP_REQUEST) {
+        /*end of session*/
+        if (strncmp(msg->first_line.u.request.method.s, "INVITE", 6) == 0) {
+            if (!(*ro_ccr_data = dlg_create_ro_session(msg, NULL, auth, dir)))
+                goto error;
+        }
+    } else {
+        goto error; //We only support Request (INVITE) messages on this interface
+    }
+
+    return 1;
+error:
+    return 0;
+}
+
+void send_ccr_interim(struct ro_session* ro_session, unsigned int used, unsigned int reserve) {
+    AAASession * auth = 0;
+
+    AAAMessage * ccr = 0;
+    Ro_CCR_t *ro_ccr_data = 0;
+    ims_information_t *ims_info = 0;
+    int32_t acc_record_type;
+    subscription_id_t subscr;
+    time_stamps_t *time_stamps;
+	struct interim_ccr *i_req = shm_malloc(sizeof(struct interim_ccr));
+
+    event_type_t *event_type;
+    int node_role = 0;
+
+	memset(i_req, 0, sizeof(sizeof(struct interim_ccr)));
+    i_req->ro_session	= ro_session;
+
+    str sip_method = str_init("dummy");
+    str sip_event = str_init("dummy");
+
+    time_t req_timestamp;
+
+    event_type = new_event_type(&sip_method, &sip_event, 0);
+
+    LM_DBG("Sending interim CCR request for (usage:new) [%i:%i] seconds for user [%.*s] using session id [%.*s]",
+    						used,
+    						reserve,
+    						ro_session->from_uri.len, ro_session->from_uri.s,
+    						ro_session->ro_session_id.len, ro_session->ro_session_id.s);
+
+    req_timestamp = time(0);
+
+    if (!(time_stamps = new_time_stamps(&req_timestamp, NULL, NULL, NULL)))
+        goto error;
+
+    if (!(ims_info = new_ims_information(event_type, time_stamps, &ro_session->callid, &ro_session->callid, &ro_session->from_uri, &ro_session->to_uri, 0, 0, 0, node_role)))
+        goto error;
+
+    LM_DBG("Created IMS information\n");
+
+    event_type = 0;
+
+    subscr.type = Subscription_Type_IMPU;
+    //TODO: need to check which direction. for ORIG we use from_uri. for TERM we use to_uri
+    subscr.id.s = ro_session->from_uri.s;
+    subscr.id.len = ro_session->from_uri.len;
+
+    acc_record_type = AAA_ACCT_INTERIM;
+
+    ro_ccr_data = new_Ro_CCR(acc_record_type, &ro_session->from_uri, ims_info, &subscr);
+    if (!ro_ccr_data) {
+        LM_ERR("dlg_create_ro_session: no memory left for generic\n");
+        goto error;
+    }
+    ims_info = NULL;
+
+    auth = cdpb.AAAGetCCAccSession(ro_session->ro_session_id);
+    if (!auth) {
+        LM_DBG("Diameter Auth Session has timed out.... creating a new one.\n");
+        /* lets try and recreate this session */
+        //TODO: make a CC App session auth = cdpb.AAASession(ro_session->auth_appid, ro_session->auth_session_type, ro_session->ro_session_id); //TODO: would like this session to last longer (see session timeout in cdp
+        //BUG("Oh shit, session timed out and I don't know how to create a new one.");
+
+        auth = cdpb.AAAMakeSession(ro_session->auth_appid, ro_session->auth_session_type, ro_session->ro_session_id); //TODO: would like this session to last longer (see session timeout in cdp
+        if (!auth)
+            goto error;
+    }
+
+    //don't send INTERIM record if session is not in OPEN state (it could already be waiting for a previous response, etc)
+    if (auth->u.cc_acc.state != ACC_CC_ST_OPEN) {
+	    LM_WARN("ignoring interim update on CC session not in correct state, currently in state [%d]\n", auth->u.cc_acc.state);
+	    goto error;
+    }
+
+    if (!(ccr = Ro_new_ccr(auth, ro_ccr_data)))
+        goto error;
+
+    if (!Ro_add_vendor_specific_appid(ccr, IMS_vendor_id_3GPP, IMS_Ro, 0/*acct id*/)) {
+        LM_ERR("Problem adding Vendor specific ID\n");
+    }
+    ro_session->hop_by_hop += 1;
+    if (!Ro_add_cc_request(ccr, RO_CC_INTERIM, ro_session->hop_by_hop)) {
+        LM_ERR("Problem adding CC-Request data\n");
+    }
+    if (!Ro_add_event_timestamp(ccr, time(NULL))) {
+        LM_ERR("Problem adding Event-Timestamp data\n");
+    }
+    str mac;
+    mac.s = "00:00:00:00:00:00";
+    mac.len = strlen(mac.s); //TODO - this is terrible
+
+    if (!Ro_add_user_equipment_info(ccr, AVP_EPC_User_Equipment_Info_Type_MAC, mac)) {
+        LM_ERR("Problem adding User-Equipment data\n");
+    }
+
+    if (!Ro_add_subscription_id(ccr, AVP_EPC_Subscription_Id_Type_End_User_SIP_URI, &(subscr.id))) {
+        LM_ERR("Problem adding Subscription ID data\n");
+    }
+
+    if (!Ro_add_multiple_service_credit_Control(ccr, interim_request_credits/*INTERIM_CREDIT_REQ_AMOUNT*/, used)) {
+        LM_ERR("Problem adding Multiple Service Credit Control data\n");
+    }
+
+    LM_DBG("Sending CCR Diameter message.\n");
+
+    cdpb.AAASessionsUnlock(auth->hash);
+
+    //AAAMessage *cca = cdpb.AAASendRecvMessageToPeer(ccr, &cfg.destination_host);
+    cdpb.AAASendMessageToPeer(ccr, &cfg.destination_host, resume_on_interim_ccr, (void *) i_req);
+
+//    cdpb.AAASessionsUnlock(auth->hash);
+
+    Ro_free_CCR(ro_ccr_data);
+
+    update_stat(interim_ccrs, 1);
+    return;
+error:
+	LM_ERR("error trying to reserve interim credit\n");
+
+	if (ro_ccr_data)
+		Ro_free_CCR(ro_ccr_data);
+
+	if (ccr)
+		cdpb.AAAFreeMessage(&ccr);
+
+    if (auth) {
+    	cdpb.AAASessionsUnlock(auth->hash);
+    	cdpb.AAADropCCAccSession(auth);
+    }
+    return;
+}
+
+static void resume_on_interim_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs) {
+	struct interim_ccr *i_req	= (struct interim_ccr *) param;
+	Ro_CCA_t * ro_cca_data = NULL;
+
+    update_stat(ccr_responses_time, elapsed_msecs);
+
+	if (!i_req) {
+		LM_ERR("This is so wrong: ro session is NULL\n");
+		goto error;
+	}
+
+	if (cca == NULL) {
+		LM_ERR("Error reserving credit for CCA.\n");
+		goto error;
+	}
+
+	ro_cca_data = Ro_parse_CCA_avps(cca);
+
+	if (ro_cca_data == NULL) {
+		LM_ERR("Could not parse CCA message response.\n");
+		goto error;
+	}
+
+	if (ro_cca_data->resultcode != 2001) {
+		LM_ERR("Got bad CCA result code [%d] - reservation failed", ro_cca_data->resultcode);
+		goto error;
+	} else {
+		LM_DBG("Valid CCA response with time chunk of [%i] and validity [%i].\n", ro_cca_data->mscc->granted_service_unit->cc_time, ro_cca_data->mscc->validity_time);
+	}
+
+	i_req->new_credit = ro_cca_data->mscc->granted_service_unit->cc_time;
+	i_req->credit_valid_for = ro_cca_data->mscc->validity_time;
+	i_req->is_final_allocation	= 0;
+
+	if (ro_cca_data->mscc->final_unit_action && (ro_cca_data->mscc->final_unit_action->action == 0))
+		i_req->is_final_allocation = 1;
+
+	Ro_free_CCA(ro_cca_data);
+	cdpb.AAAFreeMessage(&cca);
+
+	update_stat(successful_interim_ccrs, 1);
+	goto success;
+
+error:
+	if (ro_cca_data)
+		Ro_free_CCA(ro_cca_data);
+
+	if (ro_cca_data)
+		cdpb.AAAFreeMessage(&cca);
+
+	if (i_req) {
+		i_req->credit_valid_for = 0;
+		i_req->new_credit = 0;
+	}
+
+success:
+	resume_ro_session_ontimeout(i_req);
+}
+
+void send_ccr_stop(struct ro_session *ro_session) {
+    AAASession * auth = 0;
+    Ro_CCR_t * ro_ccr_data = 0;
+    AAAMessage * ccr = 0;
+    ims_information_t *ims_info = 0;
+    int32_t acc_record_type;
+    subscription_id_t subscr;
+    time_stamps_t *time_stamps;
+    unsigned int used = 0;
+
+    if (ro_session->event_type != pending) {
+        used = time(0) - ro_session->last_event_timestamp;
+    }
+
+    update_stat(billed_secs, used);
+
+    event_type_t *event_type;
+    int node_role = 0;
+
+    str sip_method = str_init("dummy");
+    str sip_event = str_init("dummy");
+
+    time_t req_timestamp;
+
+    event_type = new_event_type(&sip_method, &sip_event, 0);
+
+    LM_DBG("Sending CCR STOP request for for user:[%.*s] using session id:[%.*s] and units:[%d]\n",
+    		ro_session->from_uri.len, ro_session->from_uri.s, ro_session->ro_session_id.len, ro_session->ro_session_id.s, used);
+
+    req_timestamp = time(0);
+
+    if (!(time_stamps = new_time_stamps(&req_timestamp, NULL, NULL, NULL)))
+        goto error0;
+
+    if (!(ims_info = new_ims_information(event_type, time_stamps, &ro_session->callid, &ro_session->callid, &ro_session->from_uri, &ro_session->to_uri, 0, 0, 0, node_role)))
+        goto error0;
+    
+    event_type = 0;
+
+    subscr.type = Subscription_Type_IMPU;
+    subscr.id = ro_session->from_uri;
+
+    acc_record_type = AAA_ACCT_STOP;
+
+    ro_ccr_data = new_Ro_CCR(acc_record_type, &ro_session->from_uri, ims_info, &subscr);
+    if (!ro_ccr_data) {
+        LM_ERR("dlg_create_ro_session: no memory left for generic\n");
+        goto error0;
+    }
+    ims_info = 0;
+
+    LM_DBG("Created Ro data\n");
+
+    auth = cdpb.AAAGetCCAccSession(ro_session->ro_session_id);
+
+    if (!auth) {
+        LM_DBG("Diameter Auth Session has timed out.... creating a new one.\n");
+        /* lets try and recreate this session */
+        auth = cdpb.AAAMakeSession(ro_session->auth_appid, ro_session->auth_session_type, ro_session->ro_session_id); //TODO: would like this session to last longer (see session timeout in cdp
+        if (!auth)
+            goto error1;
+    }
+
+
+    if (!(ccr = Ro_new_ccr(auth, ro_ccr_data)))
+        goto error1;
+
+    LM_DBG("Created new CCR\n");
+
+    if (!Ro_add_vendor_specific_appid(ccr, IMS_vendor_id_3GPP, IMS_Ro, 0)) {
+        LM_ERR("Problem adding Vendor specific ID\n");
+    }
+   
+    ro_session->hop_by_hop += 1;
+    if (!Ro_add_cc_request(ccr, RO_CC_STOP, ro_session->hop_by_hop)) {
+        LM_ERR("Problem adding CC-Request data\n");
+    }
+   
+    if (!Ro_add_event_timestamp(ccr, time(NULL))) {
+        LM_ERR("Problem adding Event-Timestamp data\n");
+    }
+   
+    str mac;
+    mac.s = "00:00:00:00:00:00"; /*TODO: this is just a hack becuase we dont use this avp right now - if yuo like you can get the mac or some other info */
+    mac.len = strlen(mac.s);
+
+    if (!Ro_add_user_equipment_info(ccr, AVP_EPC_User_Equipment_Info_Type_MAC, mac)) {
+        LM_ERR("Problem adding User-Equipment data\n");
+    }
+    
+    if (!Ro_add_subscription_id(ccr, AVP_EPC_Subscription_Id_Type_End_User_SIP_URI, &ro_session->from_uri)) {
+        LM_ERR("Problem adding Subscription ID data\n");
+    }
+    
+    if (!Ro_add_multiple_service_credit_Control_stop(ccr, used)) {
+        LM_ERR("Problem adding Multiple Service Credit Control data\n");
+    }
+    
+    if (!Ro_add_termination_casue(ccr, 4)) {
+        LM_ERR("problem add Termination cause AVP to STOP record.\n");
+    }
+
+    cdpb.AAASessionsUnlock(auth->hash);
+    cdpb.AAASendMessageToPeer(ccr, &cfg.destination_host, resume_on_termination_ccr, NULL);
+
+    Ro_free_CCR(ro_ccr_data);
+
+    update_stat(final_ccrs, 1);
+    return;
+
+error1:
+    LM_ERR("error on Ro STOP record\n");
+    Ro_free_CCR(ro_ccr_data);
+
+    if (auth) {
+    	cdpb.AAASessionsUnlock(auth->hash);
+    	cdpb.AAADropCCAccSession(auth);
+    }
+
+error0:
+    return;
+
+}
+
+static void resume_on_termination_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs) {
+    Ro_CCA_t *ro_cca_data = NULL;
+
+    update_stat(ccr_responses_time, elapsed_msecs);
+
+    if (!cca) {
+    	LM_ERR("Error in termination CCR.\n");
+        return;
+    }
+
+    ro_cca_data = Ro_parse_CCA_avps(cca);
+
+    if (ro_cca_data == NULL) {
+    	LM_DBG("Could not parse CCA message response.\n");
+    	return;
+    }
+
+    if (ro_cca_data->resultcode != 2001) {
+    	LM_ERR("Got bad CCA result code for STOP record - [%d]\n", ro_cca_data->resultcode);
+        goto error;
+    }
+    else {
+    	LM_DBG("Valid CCA response for STOP record\n");
+    }
+
+    Ro_free_CCA(ro_cca_data);
+    cdpb.AAAFreeMessage(&cca);
+
+    update_stat(successful_final_ccrs, 1);
+
+    return;
+
+error:
+	Ro_free_CCA(ro_cca_data);
+}
+
+
+
+/**
+ * Send a CCR to the OCS based on the SIP message (INVITE ONLY)
+ * @param msg - SIP message
+ * @param direction - orig|term
+ * @param charge_type - IEC (Immediate Event Charging), ECUR (Event Charging with Unit Reservation), SCUR (Session Charging with Unit Reservation)
+ * @param unit_type - unused
+ * @param reservation_units - units to try to reserve
+ * @param reservation_units - config route to call when receiving a CCA
+ * @param tindex - transaction index
+ * @param tindex - transaction label
+ *
+ * @returns #CSCF_RETURN_TRUE if OK, #CSCF_RETURN_ERROR on error
+ */
+int Ro_Send_CCR(struct sip_msg *msg, str* direction, str* charge_type, str* unit_type, int reservation_units,
+						cfg_action_t* action, unsigned int tindex, unsigned int tlabel) {
+	str session_id = { 0, 0 },
+		asserted_id_uri	= { 0, 0 };
+	AAASession* cc_acc_session = NULL;
+    Ro_CCR_t * ro_ccr_data = 0;
+    AAAMessage * ccr = 0;
+    int dir = 0;
+    struct ro_session *new_session = 0;
+    struct session_setup_data *ssd = shm_malloc(sizeof(struct session_setup_data)); // lookup structure used to load session info from cdp callback on CCA
+
+    int cc_event_number = 0;						//According to IOT tests this should start at 0
+    int cc_event_type = RO_CC_START;
+
+    //make sure we can get the dialog! if not, we can't continue
+	struct dlg_cell* dlg = dlgb.get_dlg(msg);
+	if (!dlg) {
+		LM_DBG("Unable to find dialog and cannot do Ro charging without it\n");
+		goto error;
+	}
+
+	if ((asserted_id_uri = cscf_get_asserted_identity(msg)).len == 0) {
+		LM_DBG("No P-Asserted-Identity hdr found. Using From hdr");
+
+		asserted_id_uri	= dlg->from_uri;
+	}
+
+	dir = get_direction_as_int(direction);
+
+	//create a session object without auth and diameter session id - we will add this later.
+	new_session = build_new_ro_session(dir, 0, 0, &session_id, &dlg->callid,
+			&asserted_id_uri, &msg->first_line.u.request.uri, dlg->h_entry, dlg->h_id,
+			reservation_units, 0);
+
+	if (!new_session) {
+		LM_ERR("Couldn't create new Ro Session - this is BAD!\n");
+		goto error;
+	}
+
+	ssd->action	= action;
+	ssd->tindex	= tindex;
+	ssd->tlabel	= tlabel;
+	ssd->ro_session	= new_session;
+
+    if (!sip_create_ro_ccr_data(msg, dir, &ro_ccr_data, &cc_acc_session))
+        goto error;
+
+    if (!ro_ccr_data)
+        goto error;
+
+    if (!cc_acc_session)
+    	goto error;
+
+    if (!(ccr = Ro_new_ccr(cc_acc_session, ro_ccr_data)))
+        goto error;
+
+    if (!Ro_add_vendor_specific_appid(ccr, IMS_vendor_id_3GPP, IMS_Ro, 0)) {
+        LM_ERR("Problem adding Vendor specific ID\n");
+        goto error;
+    }
+
+    if (!Ro_add_cc_request(ccr, cc_event_type, cc_event_number)) {
+        LM_ERR("Problem adding CC-Request data\n");
+        goto error;
+    }
+
+    if (!Ro_add_event_timestamp(ccr, time(NULL))) {
+        LM_ERR("Problem adding Event-Timestamp data\n");
+        goto error;
+    }
+
+    str mac; //TODO - this is terrible
+    mac.s = "00:00:00:00:00:00";
+    mac.len = strlen(mac.s);
+
+    if (!Ro_add_user_equipment_info(ccr, AVP_EPC_User_Equipment_Info_Type_MAC, mac)) {
+        LM_ERR("Problem adding User-Equipment data\n");
+        goto error;
+    }
+
+    if (!Ro_add_subscription_id(ccr, AVP_EPC_Subscription_Id_Type_End_User_SIP_URI, &asserted_id_uri)) {
+        LM_ERR("Problem adding Subscription ID data\n");
+        goto error;
+    }
+    if (!Ro_add_multiple_service_credit_Control(ccr, reservation_units, -1)) {
+        LM_ERR("Problem adding Multiple Service Credit Control data\n");
+        goto error;
+    }
+
+    /* before we send, update our session object with CC App session ID and data */
+    new_session->auth_appid = cc_acc_session->application_id;
+    new_session->auth_session_type = cc_acc_session->type;
+    new_session->ro_session_id.s = (char*) shm_malloc(cc_acc_session->id.len);
+    new_session->ro_session_id.len = cc_acc_session->id.len;
+    memcpy(new_session->ro_session_id.s, cc_acc_session->id.s, cc_acc_session->id.len);
+    
+    LM_DBG("new CC Ro Session ID: [%.*s]\n", cc_acc_session->id.len, cc_acc_session->id.s);
+
+    LM_DBG("Sending CCR Diameter message.\n");
+    cdpb.AAASessionsUnlock(cc_acc_session->hash);
+    cdpb.AAASendMessageToPeer(ccr, &cfg.destination_host, resume_on_initial_ccr, (void *) ssd);
+
+    Ro_free_CCR(ro_ccr_data);
+
+    //TODO: if the following fail, we should clean up the Ro session.......
+    if (dlgb.register_dlgcb(dlg, DLGCB_RESPONSE_FWDED, dlg_reply, (void*)new_session ,NULL ) != 0) {
+    	LM_CRIT("cannot register callback for dialog confirmation\n");
+    	goto error;
+    }
+
+    if (dlgb.register_dlgcb(dlg, DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED | DLGCB_DESTROY
+    		, dlg_terminated, (void*)new_session, NULL ) != 0) {
+    	LM_CRIT("cannot register callback for dialog termination\n");
+    	goto error;
+    }
+
+    update_stat(initial_ccrs, 1);
+
+    return RO_RETURN_TRUE;
+
+error:
+    Ro_free_CCR(ro_ccr_data);
+    if (cc_acc_session) {
+        	cdpb.AAASessionsUnlock(cc_acc_session->hash);
+        	cdpb.AAADropSession(cc_acc_session);
+    }
+
+    if (ssd)
+    	pkg_free(ssd);
+
+    LM_DBG("Trying to reserve credit on initial INVITE failed.\n");
+    return RO_RETURN_ERROR;
+}
+
+static void resume_on_initial_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs) {
+    Ro_CCA_t *ro_cca_data = NULL;
+    struct cell *t = NULL;
+    struct session_setup_data *ssd = (struct session_setup_data *) param;
+    int error_code	= RO_RETURN_ERROR;
+
+    update_stat(ccr_responses_time, elapsed_msecs);
+
+    if (!cca) {
+    	LM_ERR("Error reserving credit for CCA.\n");
+    	error_code	= RO_RETURN_ERROR;
+        goto error0;
+    }
+
+    if (!ssd) {
+    	LM_ERR("Session lookup data is NULL.\n");
+    	error_code	= RO_RETURN_ERROR;
+    	goto error0;
+    }
+
+    // we make sure the transaction exists
+	if (tmb.t_lookup_ident(&t, ssd->tindex, ssd->tlabel) < 0) {
+		LM_ERR("t_continue: transaction not found\n");
+		error_code	= RO_RETURN_ERROR;
+		goto error1;
+	}
+
+	// we bring the list of AVPs of the transaction to the current context
+    set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from);
+    set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to);
+    set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from);
+    set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to);
+    set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from);
+    set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to);
+
+    ro_cca_data = Ro_parse_CCA_avps(cca);
+
+    if (!ro_cca_data) {
+    	LM_ERR("Could not parse CCA message response.\n");
+    	error_code	= RO_RETURN_ERROR;
+        goto error0;
+    }
+
+    if (ro_cca_data->resultcode != 2001) {
+    	LM_ERR("Got bad CCA result code - reservation failed");
+    	error_code	= RO_RETURN_FALSE;
+        goto error1;
+    }
+
+    LM_DBG("Valid CCA response with time chunk of [%i] and validity [%i]\n",
+    			ro_cca_data->mscc->granted_service_unit->cc_time,
+    			ro_cca_data->mscc->validity_time);
+
+    ssd->ro_session->last_event_timestamp = time(0);
+    ssd->ro_session->event_type = pending;
+    ssd->ro_session->reserved_secs = ro_cca_data->mscc->granted_service_unit->cc_time;
+    ssd->ro_session->valid_for = ro_cca_data->mscc->validity_time;
+
+    Ro_free_CCA(ro_cca_data);
+
+    LM_DBG("Freeing CCA message\n");
+    cdpb.AAAFreeMessage(&cca);
+
+    link_ro_session(ssd->ro_session, 1);            //create extra ref for the fact that dialog has a handle in the callbacks
+    unref_ro_session(ssd->ro_session, 1);
+
+    create_cca_return_code(RO_RETURN_TRUE);
+
+    if (t)
+    	tmb.unref_cell(t);
+
+    tmb.t_continue(ssd->tindex, ssd->tlabel, ssd->action);
+    shm_free(ssd);
+
+    update_stat(successful_initial_ccrs, 1);
+    return;
+
+error1:
+	Ro_free_CCA(ro_cca_data);
+
+error0:
+    LM_DBG("Trying to reserve credit on initial INVITE failed on cdp callback\n");
+    create_cca_return_code(error_code);
+
+    if (t)
+    	tmb.unref_cell(t);
+
+    tmb.t_continue(ssd->tindex, ssd->tlabel, ssd->action);
+    shm_free(ssd);
+}
+
+void remove_aaa_session(str *session_id) {
+    AAASession *session;
+
+    if ((session = cdpb.AAAGetCCAccSession(*session_id))) {
+        LM_DBG("Found AAA CC App Auth session to delete.\n");
+        cdpb.AAASessionsUnlock(session->hash);
+        cdpb.AAADropCCAccSession(session);
+    }
+}
+
+int get_direction_as_int(str* direction) {
+	char* p = direction->s;
+
+	if (direction->len > 0 && p) {
+		if (p[0]=='O' || p[0]=='o'){
+			return RO_ORIG_DIRECTION;
+		} else if (p[0]=='T' || p[0]=='t') {
+			return RO_TERM_DIRECTION;
+		}
+	}
+	return RO_UNKNOWN_DIRECTION;
+}
+
+static int create_cca_return_code(int result) {
+    int rc;
+    int_str avp_val, avp_name;
+    avp_name.s.s = RO_AVP_CCA_RETURN_CODE;
+    avp_name.s.len = RO_AVP_CCA_RETURN_CODE_LENGTH;
+
+    avp_val.n = result;
+
+    /*switch(result) {
+    case RO_RETURN_FALSE:
+    	avp_val.s.s = RO_RETURN_FALSE_STR;
+    	break;
+    case RO_RETURN_ERROR:
+    	avp_val.s.s = RO_RETURN_ERROR_STR;
+    	break;
+    default:
+    	if (result >= 0)
+    		break;
+
+    	avp_val.s.s = "??";
+    }
+
+    avp_val.s.len = 2; */
+
+    rc = add_avp(AVP_NAME_STR/*|AVP_VAL_STR*/, avp_name, avp_val);
+
+    if (rc < 0)
+        LM_ERR("Couldn't create ["RO_AVP_CCA_RETURN_CODE"] AVP\n");
+    else
+    	LM_DBG("Created AVP ["RO_AVP_CCA_RETURN_CODE"] successfully: value=[%d]\n", result);
+
+    return 1;
+}

+ 23 - 0
modules/ims_charging/ims_ro.h

@@ -0,0 +1,23 @@
+#ifndef CLIENT_RF_IMS_RO_H
+#define CLIENT_RF_IMS_RO_H
+
+#include "../../mod_fix.h"
+#include "../cdp/diameter_api.h"
+#include "ro_session_hash.h"
+
+struct interim_ccr {
+	struct ro_session* ro_session;
+	int new_credit;
+	int credit_valid_for;
+	unsigned int is_final_allocation;
+};
+
+void credit_control_session_callback(int event, void* session);
+void remove_aaa_session(str *session_id);
+int Ro_Send_CCR(struct sip_msg *msg, str* direction, str* charge_type, str* unit_type, int reservation_units, cfg_action_t* action, unsigned int tindex, unsigned int tlabel);
+//void send_ccr_interim(struct ro_session *ro_session, str* from_uri, str *to_uri, int *new_credit, int *credit_valid_for, unsigned int used, unsigned int reserve, unsigned int *is_final_allocation);
+void send_ccr_interim(struct ro_session* ro_session, unsigned int used, unsigned int reserve);
+void send_ccr_stop(struct ro_session *ro_session);
+
+
+#endif /* CLIENT_RF_IMS_RO_H */

+ 372 - 0
modules/ims_charging/mod.c

@@ -0,0 +1,372 @@
+/*
+ * mod.c
+ *
+ *  Created on: 21 Feb 2013
+ *      Author: jaybeepee
+ */
+
+#include "mod.h"
+#include "../../sr_module.h"
+#include "../../modules/dialog_ng/dlg_load.h"
+#include "../../modules/dialog_ng/dlg_hash.h"
+#include "../cdp/cdp_load.h"
+#include "../cdp_avp/mod_export.h"
+#include "../../parser/parse_to.h"
+#include "stats.h"
+#include "ro_timer.h"
+#include "ro_session_hash.h"
+#include "ims_ro.h"
+#include "config.h"
+#include "dialog.h"
+
+MODULE_VERSION
+
+/* parameters */
+char* ro_origin_host_s = "scscf.ims.smilecoms.com";
+char* ro_origin_realm_s = "ims.smilecoms.com";
+char* ro_destination_realm_s = "ims.smilecoms.com";
+char* ro_destination_host_s = "hss.ims.smilecoms.com";
+char* ro_service_context_id_root_s = "[email protected]";
+char* ro_service_context_id_ext_s = "ext";
+char* ro_service_context_id_mnc_s = "01";
+char* ro_service_context_id_mcc_s = "001";
+char* ro_service_context_id_release_s = "8";
+static int ro_session_hash_size = 4096;
+int ro_timer_buffer = 5;
+int interim_request_credits = 30;
+client_ro_cfg cfg;
+
+struct cdp_binds cdpb;
+struct dlg_binds dlgb;
+cdp_avp_bind_t *cdp_avp;
+struct tm_binds tmb;
+
+char* rx_dest_realm_s = "ims.smilecoms.com";
+str rx_dest_realm;
+/* Only used if we want to force the Ro peer usually this is configured at a stack level and the first request uses realm routing */
+//char* rx_forced_peer_s = "";
+str ro_forced_peer;
+int ro_auth_expiry = 7200;
+int cdp_event_latency = 1; /*flag: report slow processing of CDP callback events or not - default enabled */
+int cdp_event_threshold = 500; /*time in ms above which we should report slow processing of CDP callback event - default 500ms*/
+int cdp_event_latency_loglevel = 0; /*log-level to use to report slow processing of CDP callback event - default ERROR*/
+
+stat_var *initial_ccrs;
+stat_var *interim_ccrs;
+stat_var *final_ccrs;
+stat_var *successful_initial_ccrs;
+stat_var *successful_interim_ccrs;
+stat_var *successful_final_ccrs;
+stat_var *ccr_responses_time;
+stat_var *billed_secs;
+stat_var *killed_calls;
+
+/** module functions */
+static int mod_init(void);
+static int mod_child_init(int);
+static void mod_destroy(void);
+static int w_ro_ccr(struct sip_msg *msg, str* route_name, str* direction, str* charge_type, str* unit_type, int reservation_units);
+//void ro_session_ontimeout(struct ro_tl *tl);
+
+static int ro_fixup(void **param, int param_no);
+
+static cmd_export_t cmds[] = {
+		{ "Ro_CCR", 	(cmd_function) w_ro_ccr, 5, ro_fixup, 0, REQUEST_ROUTE },
+		{ 0, 0, 0, 0, 0, 0 }
+};
+
+static param_export_t params[] = {
+		{ "hash_size", 				INT_PARAM,			&ro_session_hash_size 		},
+		{ "interim_update_credits",	INT_PARAM,			&interim_request_credits 	},
+		{ "timer_buffer", 			INT_PARAM,			&ro_timer_buffer 			},
+		{ "ro_forced_peer", 		STR_PARAM, 			&ro_forced_peer.s 			},
+		{ "ro_auth_expiry",			INT_PARAM, 			&ro_auth_expiry 			},
+		{ "cdp_event_latency", 		INT_PARAM,			&cdp_event_latency 			}, /*flag: report slow processing of CDP
+																						callback events or not */
+		{ "cdp_event_threshold", 	INT_PARAM, 			&cdp_event_threshold 		}, /*time in ms above which we should
+																						report slow processing of CDP callback event*/
+		{ "cdp_event_latency_log", 	INT_PARAM, 			&cdp_event_latency_loglevel },/*log-level to use to report
+																						slow processing of CDP callback event*/
+		{ "origin_host", 			STR_PARAM, 			&ro_origin_host_s 			},
+		{ "origin_realm", 			STR_PARAM,			&ro_origin_realm_s 			},
+		{ "destination_realm", 		STR_PARAM,			&ro_destination_realm_s 	},
+		{ "destination_host", 		STR_PARAM,			&ro_destination_host_s 		},
+		{ "service_context_id_root",STR_PARAM,			&ro_service_context_id_root_s 	},
+		{ "service_context_id_ext", STR_PARAM,			&ro_service_context_id_ext_s 	},
+		{ "service_context_id_mnc", STR_PARAM,			&ro_service_context_id_mnc_s 	},
+		{ "service_context_id_mcc", STR_PARAM,			&ro_service_context_id_mcc_s 	},
+		{ "service_context_id_release",	STR_PARAM, 		&ro_service_context_id_release_s},
+		{ 0, 0, 0 }
+};
+
+stat_export_t charging_stats[] = {
+    {"initial_ccrs", STAT_NO_RESET, &initial_ccrs},
+    {"interim_ccrs", STAT_NO_RESET, &interim_ccrs},
+    {"final_ccrs", STAT_NO_RESET, &final_ccrs},
+    {"successful_initial_ccrs", STAT_NO_RESET, &successful_initial_ccrs},
+    {"successful_interim_ccr", STAT_NO_RESET, &successful_interim_ccrs},
+    {"successful_final_ccrs", STAT_NO_RESET, &successful_final_ccrs},
+    {"failed_initial_ccrs", STAT_IS_FUNC, (stat_var**) get_failed_initial_ccrs},
+    {"failed_interim_ccr", STAT_IS_FUNC, (stat_var**) get_failed_interim_ccrs},
+    {"failed_final_ccrs", STAT_IS_FUNC, (stat_var**) get_failed_final_ccrs},
+    {"ccr_avg_response_time", STAT_IS_FUNC, (stat_var**) get_ccr_avg_response_time},
+    {"ccr_responses_time", STAT_NO_RESET, &ccr_responses_time},
+    {"billed_secs", STAT_NO_RESET, &billed_secs},
+    {"killed_calls", STAT_NO_RESET, &killed_calls},
+    {0, 0, 0}
+};
+
+/** module exports */
+struct module_exports exports = { MOD_NAME, DEFAULT_DLFLAGS, /* dlopen flags */
+		cmds, 		/* Exported functions */
+		params, 	/* Exported params */
+		charging_stats,	/* exported statistics */
+		0, 			/* exported MI functions */
+		0, 			/* exported pseudo-variables */
+		0, 			/* extra processes */
+		mod_init, 	/* module initialization function */
+		0,
+		mod_destroy, 	/* module destroy functoin */
+		mod_child_init 	/* per-child init function */
+};
+
+int fix_parameters() {
+	cfg.origin_host.s = ro_origin_host_s;
+	cfg.origin_host.len = strlen(ro_origin_host_s);
+
+	cfg.origin_realm.s = ro_origin_realm_s;
+	cfg.origin_realm.len = strlen(ro_origin_realm_s);
+
+	cfg.destination_realm.s = ro_destination_realm_s;
+	cfg.destination_realm.len = strlen(ro_destination_realm_s);
+
+	cfg.destination_host.s = ro_destination_host_s;
+	cfg.destination_host.len = strlen(ro_destination_host_s);
+
+	cfg.service_context_id = shm_malloc(sizeof(str));
+	if (!cfg.service_context_id) {
+		LM_ERR("fix_parameters:not enough shm memory\n");
+		return 0;
+	}
+	cfg.service_context_id->len = strlen(ro_service_context_id_ext_s)
+			+ strlen(ro_service_context_id_mnc_s)
+			+ strlen(ro_service_context_id_mcc_s)
+			+ strlen(ro_service_context_id_release_s)
+			+ strlen(ro_service_context_id_root_s) + 5;
+	cfg.service_context_id->s =
+			pkg_malloc(cfg.service_context_id->len * sizeof (char));
+	if (!cfg.service_context_id->s) {
+		LM_ERR("fix_parameters: not enough memory!\n");
+		return 0;
+	}
+	cfg.service_context_id->len = sprintf(cfg.service_context_id->s,
+			"%s.%s.%s.%s.%s", ro_service_context_id_ext_s,
+			ro_service_context_id_mnc_s, ro_service_context_id_mcc_s,
+			ro_service_context_id_release_s, ro_service_context_id_root_s);
+	if (cfg.service_context_id->len < 0) {
+		LM_ERR("fix_parameters: error while creating service_context_id\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+static int mod_init(void) {
+	int n;
+	load_dlg_f load_dlg;
+	load_tm_f load_tm;
+
+	if (!fix_parameters()) {
+		LM_ERR("unable to set Ro configuration parameters correctly\n");
+		goto error;
+	}
+
+	/* bind to the tm module */
+	if (!(load_tm = (load_tm_f) find_export("load_tm", NO_SCRIPT, 0))) {
+		LM_ERR("Can not import load_tm. This module requires tm module\n");
+		goto error;
+	}
+	if (load_tm(&tmb) == -1)
+		goto error;
+
+	if (!(load_dlg = (load_dlg_f) find_export("load_dlg", 0, 0))) { /* bind to dialog module */
+		LM_ERR("can not import load_dlg. This module requires Kamailio dialog module.\n");
+	}
+	if (load_dlg(&dlgb) == -1) {
+		goto error;
+	}
+
+	if (load_cdp_api(&cdpb) != 0) { /* load the CDP API */
+		LM_ERR("can't load CDP API\n");
+		goto error;
+	}
+
+	if (load_dlg_api(&dlgb) != 0) { /* load the dialog API */
+		LM_ERR("can't load Dialog API\n");
+		goto error;
+	}
+
+	cdp_avp = load_cdp_avp(); /* load CDP_AVP API */
+	if (!cdp_avp) {
+		LM_ERR("can't load CDP_AVP API\n");
+		goto error;
+	}
+
+	/* init timer lists*/
+	if (init_ro_timer(ro_session_ontimeout) != 0) {
+		LM_ERR("cannot init timer list\n");
+		return -1;
+	}
+
+	/* initialized the hash table */
+	for (n = 0; n < (8 * sizeof(n)); n++) {
+		if (ro_session_hash_size == (1 << n))
+			break;
+		if (ro_session_hash_size < (1 << n)) {
+			LM_WARN("hash_size is not a power of 2 as it should be -> rounding from %d to %d\n", ro_session_hash_size, 1 << (n - 1));
+			ro_session_hash_size = 1 << (n - 1);
+		}
+	}
+
+	if (init_ro_session_table(ro_session_hash_size) < 0) {
+		LM_ERR("failed to create ro session hash table\n");
+		return -1;
+	}
+
+	/* register global timer */
+	if (register_timer(ro_timer_routine, 0/*(void*)ro_session_list*/, 1) < 0) {
+		LM_ERR("failed to register timer \n");
+		return -1;
+	}
+
+	/* init timer lists*/
+	if (init_ro_timer(ro_session_ontimeout) != 0) {
+		LM_ERR("cannot init timer list\n");
+		return -1;
+	}
+
+
+	/* bind to dialog module */
+	if (!(load_dlg = (load_dlg_f) find_export("load_dlg", 0, 0))) {
+		LM_ERR("mod_init: can not import load_dlg. This module requires Kamailio dialog moduile.\n");
+	}
+
+	if (load_dlg(&dlgb) == -1) {
+		goto error;
+	}
+
+	 /* register statistics */
+	if (register_module_stats(exports.name, charging_stats) != 0) {
+		LM_ERR("failed to register core statistics\n");
+		return -1;
+	}
+
+	/*if (register_stat(MOD_NAME, "ccr_responses_time", &ccr_responses_time, 0)) {
+		LM_ERR("failed to register core statistics\n");
+		return -1;
+	}*/
+
+	return 0;
+
+error:
+	LM_ERR("Failed to initialise ims_qos module\n");
+	return RO_RETURN_FALSE;
+
+}
+
+static int mod_child_init(int rank) {
+	return 0;
+}
+
+static void mod_destroy(void) {
+
+}
+
+static int w_ro_ccr(struct sip_msg *msg, str* route_name, str* direction, str* charge_type, str* unit_type, int reservation_units) {
+	/* PSEUDOCODE/NOTES
+	 * 1. What mode are we in - terminating or originating
+	 * 2. check request type - 	IEC - Immediate Event Charging
+	 * 							ECUR - Event Charging with Unit Reservation
+	 * 							SCUR - Session Charging with Unit Reservation
+	 * 3. probably only do SCUR in this module for now - can see event based charging in another component instead (AS for SMS for example, etc)
+	 * 4. Check a dialog exists for call, if not we fail
+	 * 5. make sure we dont already have an Ro Session for this dialog
+	 * 6. create new Ro Session
+	 * 7. register for DLG callback passing new Ro session as parameter - (if dlg torn down we know which Ro session it is associated with)
+	 *
+	 *
+	 */
+	cfg_action_t* cfg_action;
+	tm_cell_t *t;
+	unsigned int tindex = 0,
+				 tlabel = 0;
+
+	LM_DBG("Ro CCR initiated: direction:%.*s, charge_type:%.*s, unit_type:%.*s, reservation_units:%i, route_name:%.*s",
+			direction->len, direction->s,
+			charge_type->len, charge_type->s,
+			unit_type->len, unit_type->s,
+			reservation_units,
+			route_name->len, route_name->s);
+
+    if (msg->first_line.type != SIP_REQUEST) {
+    	LM_ERR("Ro_CCR() called from SIP reply.");
+    	return -1;
+    }
+
+	LM_DBG("Looking for route block [%.*s]\n", route_name->len, route_name->s);
+
+	int ri = route_get(&main_rt, route_name->s);
+	if (ri < 0) {
+		LM_ERR("unable to find route block [%.*s]\n", route_name->len, route_name->s);
+		return RO_RETURN_ERROR;
+	}
+	
+	cfg_action = main_rt.rlist[ri];
+	if (!cfg_action) {
+		LM_ERR("empty action lists in route block [%.*s]\n", route_name->len, route_name->s);
+		return RO_RETURN_ERROR;
+    }
+
+	//before we send lets suspend the transaction
+	t = tmb.t_gett();
+	if (t == NULL || t == T_UNDEFINED) {
+		if (tmb.t_newtran(msg) < 0) {
+			LM_ERR("cannot create the transaction for CCR async\n");
+			return RO_RETURN_ERROR;
+		}
+		t = tmb.t_gett();
+		if (t == NULL || t == T_UNDEFINED) {
+			LM_ERR("cannot lookup the transaction\n");
+			return RO_RETURN_ERROR;
+		}
+	}
+
+	LM_DBG("Suspending SIP TM transaction\n");
+	if (tmb.t_suspend(msg, &tindex, &tlabel) < 0) {
+		LM_ERR("failed to suspend the TM processing\n");
+		return RO_RETURN_ERROR;
+	}
+
+	return Ro_Send_CCR(msg, direction, charge_type, unit_type, reservation_units, cfg_action, tindex, tlabel);
+}
+
+static int ro_fixup(void **param, int param_no) {
+	str s;
+	unsigned int num;
+
+	if (param_no > 0 && param_no <= 4) {
+		return fixup_var_str_12(param, param_no);
+	} else if (param_no == 5) {
+		/*convert to int */
+		s.s = (char*)*param;
+		s.len = strlen(s.s);
+		if (str2int(&s, &num)==0) {
+			pkg_free(*param);
+			*param = (void*)(unsigned long)num;
+			return 0;
+		}
+		LM_ERR("Bad reservation units: <%s>n", (char*)(*param));
+		return E_CFG;
+	}
+	return 0;
+}

+ 34 - 0
modules/ims_charging/mod.h

@@ -0,0 +1,34 @@
+/*
+ * mod.h
+ *
+ *  Created on: 21 Feb 2013
+ *      Author: jaybeepee
+ */
+
+#ifndef MOD_H_
+#define MOD_H_
+
+#define MOD_NAME "ims_charging"
+
+#define RO_CC_START 	1
+#define RO_CC_INTERIM 	2
+#define RO_CC_STOP 		3
+
+#define RO_UNKNOWN_DIRECTION 0
+#define RO_ORIG_DIRECTION 1
+#define RO_TERM_DIRECTION 2
+
+/** Return and break the execution of routing script */
+#define RO_RETURN_BREAK	0
+/** Return true in the routing script */
+#define RO_RETURN_TRUE	1
+/** Return false in the routing script */
+#define RO_RETURN_FALSE -1
+#define RO_RETURN_FALSE_STR "-1"
+/** Return error in the routing script */
+#define RO_RETURN_ERROR -2
+#define RO_RETURN_ERROR_STR "-2"
+
+#define RO_AVP_CCA_RETURN_CODE "cca_return_code"
+#define RO_AVP_CCA_RETURN_CODE_LENGTH 15
+#endif /* MOD_H_ */

+ 19 - 0
modules/ims_charging/ro_fixup.c

@@ -0,0 +1,19 @@
+/* 
+ * File:   ro_fixup.c
+ * Author: Jason Penton
+ *
+ * Created on 06 April 2011, 9:52 PM
+ */
+
+#include "ro_fixup.h"
+#include "../../mod_fix.h"
+
+int ro_send_ccr_fixup(void** param, int param_no) {
+    if (strlen((char*) *param) <= 0) {
+        LM_ERR("empty parameter %d not allowed\n", param_no);
+        return -1;
+    }
+
+    return fixup_var_int_12(param, 1);
+}
+

+ 14 - 0
modules/ims_charging/ro_fixup.h

@@ -0,0 +1,14 @@
+/* 
+ * File:   ro_fixup.h
+ * Author: root
+ *
+ * Created on 06 April 2011, 9:51 PM
+ */
+
+#ifndef RO_FIXUP_H
+#define	RO_FIXUP_H
+
+int ro_send_ccr_fixup(void** param, int param_no);
+
+#endif	/* RO_FIXUP_H */
+

+ 277 - 0
modules/ims_charging/ro_session_hash.c

@@ -0,0 +1,277 @@
+/* 
+ * File:   ro_session_hash.c
+ * Author: Jason Penton
+ *
+ * Created on 08 April 2011, 1:10 PM
+ */
+
+#include "ro_session_hash.h"
+
+#define MAX_LDG_LOCKS  2048
+#define MIN_LDG_LOCKS  2
+
+/*! global ro_session table */
+struct ro_session_table *ro_session_table = 0;
+
+/*!
+ * \brief Link a ro_session structure
+ * \param ro_session Ro Session
+ * \param n extra increments for the reference counter
+ */
+void link_ro_session(struct ro_session *ro_session, int n) {
+    struct ro_session_entry *ro_session_entry;
+
+    ro_session_entry = &(ro_session_table->entries[ro_session->h_entry]);
+
+    ro_session_lock(ro_session_table, ro_session_entry);
+
+    ro_session->h_id = ro_session_entry->next_id++;
+    if (ro_session_entry->first == 0) {
+        ro_session_entry->first = ro_session_entry->last = ro_session;
+    } else {
+        ro_session_entry->last->next = ro_session;
+        ro_session->prev = ro_session_entry->last;
+        ro_session_entry->last = ro_session;
+    }
+
+    ro_session->ref += 1 + n;
+    
+    ro_session_unlock(ro_session_table, ro_session_entry);
+
+    return;
+}
+
+/*!
+ * \brief Refefence an ro_session with locking
+ * \see ref_ro_session_unsafe
+ * \param ro_session Ro Session
+ * \param cnt increment for the reference counter
+ */
+void ref_ro_session(struct ro_session *ro_session, unsigned int cnt) {
+    struct ro_session_entry *ro_session_entry;
+
+    ro_session_entry = &(ro_session_table->entries[ro_session->h_entry]);
+
+    ro_session_lock(ro_session_table, ro_session_entry);
+    ref_ro_session_unsafe(ro_session, cnt);
+    ro_session_unlock(ro_session_table, ro_session_entry);
+}
+
+/*!
+ * \brief Unreference an ro_session with locking
+ * \see unref_ro_session_unsafe
+ * \param ro_session Ro Session
+ * \param cnt decrement for the reference counter
+ */
+void unref_ro_session(struct ro_session *ro_session, unsigned int cnt) {
+    struct ro_session_entry *ro_session_entry;
+
+    ro_session_entry = &(ro_session_table->entries[ro_session->h_entry]);
+
+    ro_session_lock(ro_session_table, ro_session_entry);
+    unref_ro_session_unsafe(ro_session, cnt, ro_session_entry);
+    ro_session_unlock(ro_session_table, ro_session_entry);
+}
+
+/*!
+ * \brief Initialize the global ro_session table
+ * \param size size of the table
+ * \return 0 on success, -1 on failure
+ */
+int init_ro_session_table(unsigned int size) {
+    unsigned int n;
+    unsigned int i;
+
+    ro_session_table = (struct ro_session_table*) shm_malloc(sizeof (struct ro_session_table) +size * sizeof (struct ro_session_entry));
+    if (ro_session_table == 0) {
+        LM_ERR("no more shm mem (1)\n");
+        goto error0;
+    }
+
+    memset(ro_session_table, 0, sizeof (struct ro_session_table));
+    ro_session_table->size = size;
+    ro_session_table->entries = (struct ro_session_entry*) (ro_session_table + 1);
+
+    n = (size < MAX_LDG_LOCKS) ? size : MAX_LDG_LOCKS;
+    for (; n >= MIN_LDG_LOCKS; n--) {
+        ro_session_table->locks = lock_set_alloc(n);
+        if (ro_session_table->locks == 0)
+            continue;
+        if (lock_set_init(ro_session_table->locks) == 0) {
+            lock_set_dealloc(ro_session_table->locks);
+            ro_session_table->locks = 0;
+            continue;
+        }
+        ro_session_table->locks_no = n;
+        break;
+    }
+
+    if (ro_session_table->locks == 0) {
+        LM_ERR("unable to allocted at least %d locks for the hash table\n",
+                MIN_LDG_LOCKS);
+        goto error1;
+    }
+
+    for (i = 0; i < size; i++) {
+        memset(&(ro_session_table->entries[i]), 0, sizeof (struct ro_session_entry));
+        ro_session_table->entries[i].next_id = rand();
+        ro_session_table->entries[i].lock_idx = i % ro_session_table->locks_no;
+    }
+
+    return 0;
+error1:
+    shm_free(ro_session_table);
+error0:
+    return -1;
+}
+
+/*!
+ * \brief Destroy an ro_session and free memory
+ * \param ro_session destroyed Ro Session
+ */
+inline void destroy_ro_session(struct ro_session *ro_session) {
+
+    LM_DBG("destroying Ro Session %p\n", ro_session);
+
+    remove_ro_timer(&ro_session->ro_tl);
+    
+    if (ro_session->ro_session_id.s && (ro_session->ro_session_id.len > 0)) {
+        shm_free(ro_session->ro_session_id.s);
+    }
+    shm_free(ro_session);
+}
+
+/*!
+ * \brief Destroy the global Ro session table
+ */
+void destroy_dlg_table(void) {
+    struct ro_session *ro_session, *l_ro_session;
+    unsigned int i;
+
+    if (ro_session_table == 0)
+        return;
+
+    if (ro_session_table->locks) {
+        lock_set_destroy(ro_session_table->locks);
+        lock_set_dealloc(ro_session_table->locks);
+    }
+
+    for (i = 0; i < ro_session_table->size; i++) {
+        ro_session = ro_session_table->entries[i].first;
+        while (ro_session) {
+            l_ro_session = ro_session;
+            ro_session = ro_session->next;
+            destroy_ro_session(l_ro_session);
+        }
+
+    }
+
+    shm_free(ro_session_table);
+    ro_session_table = 0;
+
+    return;
+}
+
+struct ro_session* build_new_ro_session(int direction, int auth_appid, int auth_session_type, str *session_id, str *callid, str *from_uri, str* to_uri, unsigned int dlg_h_entry, unsigned int dlg_h_id, unsigned int requested_secs, unsigned int validity_timeout){
+    LM_DBG("Building Ro Session **********");
+    char *p;
+    unsigned int len = session_id->len + callid->len + from_uri->len + to_uri->len + sizeof (struct ro_session);
+    struct ro_session *new_ro_session = (struct ro_session*) shm_malloc(len);
+
+    if (!new_ro_session) {
+        LM_ERR("no more shm mem.\n");
+        shm_free(new_ro_session);
+        return 0;
+    }
+    
+    LM_DBG("New Ro Session given memory at address [%p]\n", new_ro_session);
+
+    memset(new_ro_session, 0, len);
+
+    new_ro_session->direction = direction;
+    new_ro_session->auth_appid = auth_appid;
+    new_ro_session->auth_session_type = auth_session_type;
+
+    new_ro_session->ro_tl.next = new_ro_session->ro_tl.prev;
+    new_ro_session->ro_tl.timeout = 0; //requested_secs;
+
+    new_ro_session->reserved_secs = requested_secs;
+    new_ro_session->valid_for = validity_timeout;
+
+    new_ro_session->hop_by_hop = 1;
+    new_ro_session->next = 0;
+    new_ro_session->dlg_h_entry = dlg_h_entry;
+    new_ro_session->dlg_h_id = dlg_h_id;
+
+    new_ro_session->h_entry = dlg_h_entry; /* we will use the same entry ID as the dlg - saves us using our own hash function */
+    new_ro_session->h_id = 0;
+    new_ro_session->ref = 0;
+
+    p = (char*) (new_ro_session + 1);
+    new_ro_session->callid.s = p;
+    new_ro_session->callid.len = callid->len;
+    memcpy(p, callid->s, callid->len);
+    p += callid->len;
+
+    new_ro_session->ro_session_id.s = p;
+    new_ro_session->ro_session_id.len = session_id->len;
+    memcpy(p, session_id->s, session_id->len);
+    p += session_id->len;
+
+    new_ro_session->from_uri.s = p;
+    new_ro_session->from_uri.len = from_uri->len;
+    memcpy(p, from_uri->s, from_uri->len);
+    p += from_uri->len;
+
+    new_ro_session->to_uri.s = p;
+    new_ro_session->to_uri.len = to_uri->len;
+    memcpy(p, to_uri->s, to_uri->len);
+    p += to_uri->len;
+
+
+    if (p != (((char*) new_ro_session) + len)) {
+        LM_ERR("buffer overflow\n");
+        shm_free(new_ro_session);
+        return 0;
+    }
+
+    return new_ro_session;
+
+}
+
+/*!
+ * \brief Lookup an Ro session in the global list
+ * \param h_entry number of the hash table entry
+ * \param h_id id of the hash table entry
+ * \return ro_session on success, NULL on failure
+ */
+struct ro_session* lookup_ro_session(unsigned int h_entry, str* callid, int direction, unsigned int *del) {
+    struct ro_session *ro_session;
+    struct ro_session_entry *ro_session_entry;
+
+    if (del != NULL)
+        *del = 0;
+
+    if (h_entry >= ro_session_table->size)
+        goto not_found;
+    ro_session_entry = &(ro_session_table->entries[h_entry]);
+
+    ro_session_lock(ro_session_table, ro_session_entry);
+
+    for (ro_session = ro_session_entry->first; ro_session; ro_session = ro_session->next) {
+        if ((direction==0 || direction==ro_session->direction) && (strncmp(ro_session->callid.s, callid->s, callid->len)==0)) {
+		ref_ro_session_unsafe(ro_session,1);
+            LM_DBG("ref ro_session %p with 1 -> %d\n", ro_session, ro_session->ref);
+            ro_session_unlock(ro_session_table, ro_session_entry);
+            LM_DBG("ro_session id=%u found on entry %u\n", ro_session->h_id, h_entry);
+            return ro_session;
+        }
+    }
+
+    ro_session_unlock(ro_session_table, ro_session_entry);
+not_found:
+    LM_DBG("no ro_session for callid=%.*s found on entry %u\n", callid->len, callid->s, h_entry);
+    return 0;
+}
+
+

+ 189 - 0
modules/ims_charging/ro_session_hash.h

@@ -0,0 +1,189 @@
+/* 
+ * File:   ro_session_hash.h
+ * Author: Jason Penton
+ *
+ * Created on 07 April 2011, 4:12 PM
+ */
+
+#ifndef RO_SESSION_HASH_H
+#define	RO_SESSION_HASH_H
+
+#include "ro_timer.h"
+#include "../../mem/shm_mem.h"
+#include <stdlib.h>
+
+enum ro_session_event_type {
+    pending,
+    answered,
+    no_more_credit,
+    unknown_error
+};
+
+struct ro_session {
+	str cdp_session_id;
+    volatile int ref;
+    int direction;
+    struct ro_session* next;
+    struct ro_session* prev;
+    str ro_session_id;
+    str callid;
+    str from_uri;
+    str to_uri;
+    unsigned int hop_by_hop;
+    struct ro_tl ro_tl;
+    unsigned int reserved_secs;
+    unsigned int valid_for;
+    unsigned int dlg_h_entry;
+    unsigned int dlg_h_id;
+    unsigned int h_entry;
+    unsigned int h_id;
+    time_t start_time;
+    time_t last_event_timestamp;
+    enum ro_session_event_type event_type;
+    int auth_appid;
+    int auth_session_type;
+    int active;
+};
+
+/*! entries in the main ro_session table */
+struct ro_session_entry {
+    struct ro_session *first; /*!< dialog list */
+    struct ro_session *last; /*!< optimisation, end of the dialog list */
+    unsigned int next_id; /*!< next id */
+    unsigned int lock_idx; /*!< lock index */
+};
+
+/*! main ro_sesion table */
+struct ro_session_table {
+    unsigned int size; /*!< size of the dialog table */
+    struct ro_session_entry *entries; /*!< dialog hash table */
+    unsigned int locks_no; /*!< number of locks */
+    gen_lock_set_t *locks; /*!< lock table */
+};
+
+
+/*! global ro_session table */
+extern struct ro_session_table *ro_session_table;
+
+
+/*!
+ * \brief Set a ro_session lock
+ * \param _table ro_session table
+ * \param _entry locked entry
+ */
+#define ro_session_lock(_table, _entry) \
+		lock_set_get( (_table)->locks, (_entry)->lock_idx);
+
+
+/*!
+ * \brief Release a ro_session lock
+ * \param _table ro_session table
+ * \param _entry locked entry
+ */
+#define ro_session_unlock(_table, _entry) \
+		lock_set_release( (_table)->locks, (_entry)->lock_idx);
+
+/*!
+ * \brief Reference an ro_session without locking
+ * \param _ro_session Ro Session
+ * \param _cnt increment for the reference counter
+ */
+#define ref_ro_session_unsafe(_session,_cnt)     \
+	do { \
+		(_session)->ref += (_cnt); \
+		LM_DBG("ref ro_session %p with %d -> %d (tl=%p)\n", \
+			(_session),(_cnt),(_session)->ref,&(_session)->ro_tl); \
+	}while(0)
+
+
+/*!
+ * \brief Unreference an ro_session without locking
+ * \param _ro_session Ro Session
+ * \param _cnt decrement for the reference counter
+ */
+#define unref_ro_session_unsafe(_ro_session,_cnt,_ro_session_entry)   \
+	do { \
+		(_ro_session)->ref -= (_cnt); \
+		LM_DBG("unref ro_session %p with %d -> %d (tl=%p)\n",\
+			(_ro_session),(_cnt),(_ro_session)->ref,&(_ro_session)->ro_tl);\
+		if ((_ro_session)->ref<0) {\
+			LM_CRIT("bogus ref for session id < 0 [%d]\n",(_ro_session)->ref);\
+		}\
+		if ((_ro_session)->ref<=0) { \
+			unlink_unsafe_ro_session( _ro_session_entry, _ro_session);\
+			LM_DBG("ref <=0 for ro_session %p\n",_ro_session);\
+			destroy_ro_session(_ro_session);\
+		}\
+	}while(0)
+
+/*!
+ * \brief Unlink a ro_session from the list without locking
+ * \see unref_ro_session_unsafe
+ * \param ro_session_entry unlinked entry
+ * \param ro_session unlinked ro_session
+ */
+static inline void unlink_unsafe_ro_session(struct ro_session_entry *ro_session_entry, struct ro_session *ro_session) {
+    if (ro_session->next)
+        ro_session->next->prev = ro_session->prev;
+    else
+        ro_session_entry->last = ro_session->prev;
+    if (ro_session->prev)
+        ro_session->prev->next = ro_session->next;
+    else
+        ro_session_entry->first = ro_session->next;
+
+    ro_session->next = ro_session->prev = 0;
+
+    return;
+}
+
+/*!
+ * \brief Initialize the global ro_session table
+ * \param size size of the table
+ * \return 0 on success, -1 on failure
+ */
+int init_ro_session_table(unsigned int size);
+
+/*!
+ * \brief Destroy a ro_session and free memory
+ * \param ro_session destroyed Ro Session
+ */
+inline void destroy_ro_session(struct ro_session *ro_session);
+
+/*!
+ * \brief Destroy the ro_session dialog table
+ */
+void destroy_ro_session_table(void);
+
+/*!
+ * \brief Link a ro_session structure
+ * \param ro_session Ro Session
+ * \param n extra increments for the reference counter
+ */
+void link_ro_session(struct ro_session *ro_session, int n);
+
+void remove_aaa_session(str *session_id);
+
+struct ro_session* build_new_ro_session(int direction, int auth_appid, int auth_session_type, str *session_id, str *callid, str *from_uri, str* to_uri, unsigned int dlg_h_entry, unsigned int dlg_h_id, unsigned int requested_secs, unsigned int validity_timeout);
+
+/*!
+ * \brief Refefence a ro_session with locking
+ * \see ref_ro_session_unsafe
+ * \param ro_session Ro Session
+ * \param cnt increment for the reference counter
+ */
+void ref_ro_session(struct ro_session *ro_session, unsigned int cnt);
+
+/*!
+ * \brief Unreference a ro_session with locking
+ * \see unref_ro_session_unsafe
+ * \param ro_session Ro Session
+ * \param cnt decrement for the reference counter
+ */
+void unref_ro_session(struct ro_session *ro_session, unsigned int cnt);
+
+struct ro_session* lookup_ro_session(unsigned int h_entry, str *callid, int direction, unsigned int *del);
+
+
+#endif	/* RO_SESSION_HASH_H */
+

+ 443 - 0
modules/ims_charging/ro_timer.c

@@ -0,0 +1,443 @@
+/* 
+ * File:   ro_timer.c
+ * Author: Jason Penton
+ *
+ * Created on 06 April 2011, 1:37 PM
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../../mem/shm_mem.h"
+#include "../dialog_ng/dlg_load.h"
+#include "ro_timer.h"
+#include "ro_session_hash.h"
+#include "ims_ro.h"
+#include "stats.h"
+
+extern int interim_request_credits;
+extern int ro_timer_buffer;
+
+extern struct dlg_binds dlgb;
+
+/*! global dialog timer */
+struct ro_timer *roi_timer = 0;
+/*! global dialog timer handler */
+ro_timer_handler timer_hdl = 0;
+
+/*!
+ * \brief Initialize the ro_session timer handler
+ * Initialize the ro_session timer handler, allocate the lock and a global
+ * timer in shared memory. The global timer handler will be set on success.
+ * \param hdl ro_session timer handler
+ * \return 0 on success, -1 on failure
+ */
+int init_ro_timer(ro_timer_handler hdl) {
+    roi_timer = (struct ro_timer*) shm_malloc(sizeof (struct ro_timer));
+    if (roi_timer == 0) {
+        LM_ERR("no more shm mem\n");
+        return -1;
+    }
+    memset(roi_timer, 0, sizeof (struct ro_timer));
+
+    roi_timer->first.next = roi_timer->first.prev = &(roi_timer->first);
+
+    roi_timer->lock = lock_alloc();
+    if (roi_timer->lock == 0) {
+        LM_ERR("failed to alloc lock\n");
+        goto error0;
+    }
+
+    if (lock_init(roi_timer->lock) == 0) {
+        LM_ERR("failed to init lock\n");
+        goto error1;
+    }
+
+    timer_hdl = hdl;
+    return 0;
+error1:
+    lock_dealloc(roi_timer->lock);
+error0:
+    shm_free(roi_timer);
+    roi_timer = 0;
+    return -1;
+}
+
+/*!
+ * \brief Destroy global ro_session timer
+ */
+void destroy_ro_timer(void) {
+    if (roi_timer == 0)
+        return;
+
+    lock_destroy(roi_timer->lock);
+    lock_dealloc(roi_timer->lock);
+
+    shm_free(roi_timer);
+    roi_timer = 0;
+}
+
+/*!
+ * \brief Helper function for insert_ro_session_timer
+ * \see insert_ro_session_timer
+ * \param tl ro_session timer list
+ */
+static inline void insert_ro_timer_unsafe(struct ro_tl *tl) {
+    struct ro_tl* ptr;
+
+    /* insert in sorted order */
+    for (ptr = roi_timer->first.prev; ptr != &roi_timer->first; ptr = ptr->prev) {
+        if (ptr->timeout <= tl->timeout)
+            break;
+    }
+
+    LM_DBG("inserting %p for %d\n", tl, tl->timeout);
+    tl->prev = ptr;
+    tl->next = ptr->next;
+    tl->prev->next = tl;
+    tl->next->prev = tl;
+}
+
+/*!
+ * \brief Insert a ro_session timer to the list
+ * \param tl ro_session timer list
+ * \param interval timeout value in seconds
+ * \return 0 on success, -1 when the input timer list is invalid
+ */
+int insert_ro_timer(struct ro_tl *tl, int interval) {
+    lock_get(roi_timer->lock);
+
+    LM_DBG("inserting timer for interval [%i]\n", interval);
+    if (tl->next != 0 || tl->prev != 0) {
+        lock_release(roi_timer->lock);
+        LM_CRIT("Trying to insert a bogus ro tl=%p tl->next=%p tl->prev=%p\n",
+                tl, tl->next, tl->prev);
+        return -1;
+    }
+    tl->timeout = get_ticks() + interval;
+    insert_ro_timer_unsafe(tl);
+
+    lock_release(roi_timer->lock);
+
+    return 0;
+}
+
+/*!
+ * \brief Helper function for remove_ro_session_timer
+ * \param tl ro_session timer list
+ * \see remove_ro_session_timer
+ */
+static inline void remove_ro_timer_unsafe(struct ro_tl *tl) {
+    tl->prev->next = tl->next;
+    tl->next->prev = tl->prev;
+}
+
+/*!
+ * \brief Remove a ro_session timer from the list
+ * \param tl ro_session timer that should be removed
+ * \return 1 when the input timer is empty, 0 when the timer was removed,
+ * -1 when the input timer list is invalid
+ */
+int remove_ro_timer(struct ro_tl *tl) {
+    lock_get(roi_timer->lock);
+
+    if (tl->prev == NULL && tl->timeout == 0) {
+        lock_release(roi_timer->lock);
+        return 1;
+    }
+
+    if (tl->prev == NULL || tl->next == NULL) {
+        LM_CRIT("bogus tl=%p tl->prev=%p tl->next=%p\n",
+                tl, tl->prev, tl->next);
+        lock_release(roi_timer->lock);
+        return -1;
+    }
+
+    remove_ro_timer_unsafe(tl);
+    tl->next = NULL;
+    tl->prev = NULL;
+    tl->timeout = 0;
+
+    lock_release(roi_timer->lock);
+    return 0;
+}
+
+/*!
+ * \brief Update a ro_session timer on the list
+ * \param tl dialog timer
+ * \param timeout new timeout value in seconds
+ * \return 0 on success, -1 when the input list is invalid
+ * \note the update is implemented as a remove, insert
+ */
+int update_ro_timer(struct ro_tl *tl, int timeout) {
+    lock_get(roi_timer->lock);
+
+    if (tl->next) {
+        if (tl->prev == 0) {
+            lock_release(roi_timer->lock);
+            return -1;
+        }
+        remove_ro_timer_unsafe(tl);
+    }
+
+    tl->timeout = get_ticks() + timeout;
+    insert_ro_timer_unsafe(tl);
+
+    lock_release(roi_timer->lock);
+    return 0;
+}
+
+/*!
+ * \brief Helper function for ro_timer_routine
+ * \param time time for expiration check
+ * \return list of expired credit reservations on sessions on success, 0 on failure
+ */
+static inline struct ro_tl* get_expired_ro_sessions(unsigned int time) {
+    struct ro_tl *tl, *end, *ret;
+
+    lock_get(roi_timer->lock);
+
+    if (roi_timer->first.next == &(roi_timer->first) || roi_timer->first.next->timeout > time) {
+        lock_release(roi_timer->lock);
+        return 0;
+    }
+
+    end = &roi_timer->first;
+    tl = roi_timer->first.next;
+    LM_DBG("start with tl=%p tl->prev=%p tl->next=%p (%d) at %d and end with end=%p end->prev=%p end->next=%p\n", tl, tl->prev, tl->next, tl->timeout, time, end, end->prev, end->next);
+    while (tl != end && tl->timeout <= time) {
+        LM_DBG("getting tl=%p tl->prev=%p tl->next=%p with %d\n", tl, tl->prev, tl->next, tl->timeout);
+        tl->prev = 0;
+        tl->timeout = 0;
+        tl = tl->next;
+    }
+    LM_DBG("end with tl=%p tl->prev=%p tl->next=%p and d_timer->first.next->prev=%p\n", tl, tl->prev, tl->next, roi_timer->first.next->prev);
+
+    if (tl == end && roi_timer->first.next->prev) {
+        ret = 0;
+    } else {
+        ret = roi_timer->first.next;
+        tl->prev->next = 0;
+        roi_timer->first.next = tl;
+        tl->prev = &roi_timer->first;
+    }
+
+    lock_release(roi_timer->lock);
+
+    return ret;
+}
+
+/*!
+ * \brief Timer routine for expiration of credit reservations
+ * Timer handler for expiration of credit reservations on a session, runs the global timer handler on them.
+ * \param time for expiration checks
+ * \param attr unused
+ */
+void ro_timer_routine(unsigned int ticks, void * attr) {
+
+    struct ro_tl *tl, *ctl;
+    LM_DBG("getting expired ro-sessions");
+
+    tl = get_expired_ro_sessions(ticks);
+
+    while (tl) {
+        ctl = tl;
+        tl = tl->next;
+        ctl->next = NULL;
+        LM_DBG("Ro Session Timer firing: tl=%p next=%p\n", ctl, tl);
+        timer_hdl(ctl);
+    }
+}
+
+void resume_ro_session_ontimeout(struct interim_ccr *i_req) {
+	time_t now = time(0);
+	time_t used_secs;
+	struct ro_session_entry *ro_session_entry = NULL;
+
+	if (!i_req) {
+		LM_ERR("This is so wrong: i_req is NULL\n");
+		return;
+	}
+
+	LM_DBG("credit=%d credit_valid_for=%d", i_req->new_credit, i_req->credit_valid_for);
+
+	used_secs = now - i_req->ro_session->last_event_timestamp;
+
+	/* check to make sure diameter server is giving us sane values */
+	if (i_req->new_credit > i_req->credit_valid_for) {
+		LM_WARN("That's weird, Diameter server gave us credit with a lower validity period :D. Setting reserved time to validity perioud instead \n");
+		i_req->new_credit = i_req->credit_valid_for;
+	}
+
+	if (i_req->new_credit > 0) {
+		//now insert the new timer
+		i_req->ro_session->last_event_timestamp = time(0);
+		i_req->ro_session->event_type = answered;
+		i_req->ro_session->valid_for = i_req->credit_valid_for;
+
+		int ret = 0;
+		if (i_req->is_final_allocation) {
+			LM_DBG("This is a final allocation and call will end in %i seconds\n", i_req->new_credit);
+			i_req->ro_session->event_type = no_more_credit;
+			ret = insert_ro_timer(&i_req->ro_session->ro_tl, i_req->new_credit);
+		}
+		else {
+			int timer_timeout = i_req->new_credit;
+
+			if (i_req->new_credit > ro_timer_buffer /*TIMEOUTBUFFER*/) {
+
+				// We haven't finished using our 1st block of units, and we need to set the timer to
+				// (new_credit - ro_timer_buffer[5 secs]) to ensure we get new credit before our previous
+				// reservation is exhausted. This will only be done the first time, because the timer
+				// will always be fired 5 seconds before we run out of time thanks to this operation
+
+				if ((now - i_req->ro_session->start_time) /* call time */ < i_req->ro_session->reserved_secs)
+					timer_timeout = i_req->new_credit - ro_timer_buffer;
+				else
+					timer_timeout = i_req->new_credit;
+			}
+
+			ret = insert_ro_timer(&i_req->ro_session->ro_tl, timer_timeout);
+
+		}
+
+		// update to the new block of units we got
+		i_req->ro_session->reserved_secs = i_req->new_credit;
+
+		if (ret != 0) {
+			LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
+					i_req->ro_session->ro_session_id.len, i_req->ro_session->ro_session_id.s);
+		}
+		else {
+			ref_ro_session_unsafe(i_req->ro_session, 1);
+		}
+	}
+	else {
+		/* just put the timer back in with however many seconds are left (if any!!! in which case we need to kill */
+		/* also update the event type to no_more_credit to save on processing the next time we get here */
+		i_req->ro_session->event_type = no_more_credit;
+		int whatsleft = i_req->ro_session->reserved_secs - used_secs;
+		if (whatsleft <= 0) {
+			LM_WARN("Immediately killing call due to no more credit\n");
+			dlgb.lookup_terminate_dlg(i_req->ro_session->dlg_h_entry, i_req->ro_session->dlg_h_id, NULL );
+		}
+		else {
+			LM_DBG("No more credit for user - letting call run out of money in [%i] seconds", whatsleft);
+			int ret = insert_ro_timer(&i_req->ro_session->ro_tl, whatsleft);
+			if (ret != 0) {
+				LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
+						i_req->ro_session->ro_session_id.len, i_req->ro_session->ro_session_id.s);
+			}
+			else {
+				ref_ro_session_unsafe(i_req->ro_session, 1);
+			}
+		}
+	}
+
+	ro_session_entry = &(ro_session_table->entries[i_req->ro_session->h_entry]);
+
+	ro_session_unlock(ro_session_table, ro_session_entry);
+	unref_ro_session(i_req->ro_session, 1);//unref from the initial timer that fired this event.
+
+	shm_free(i_req);
+	LM_DBG("Exiting async ccr interim nicely");
+}
+
+/* this is the function called when a we need to request more funds/credit. We need to try and reserve more credit.
+ * If we cant we need to put a new timer to kill the call at the appropriate time
+ */
+void ro_session_ontimeout(struct ro_tl *tl) {
+	time_t now,  used_secs, call_time;
+
+	LM_DBG("We have a fired timer [p=%p] and tl=[%i].\n", tl, tl->timeout);
+
+	/* find the session id for this timer*/
+	struct ro_session_entry *ro_session_entry = NULL;
+
+	struct ro_session* ro_session;
+	ro_session = ((struct ro_session*) ((char *) (tl)
+			- (unsigned long) (&((struct ro_session*) 0)->ro_tl)));
+
+	if (!ro_session) {
+		LM_ERR("Can't find a session. This is bad");
+		return;
+	}
+	
+	ro_session_entry = &(ro_session_table->entries[ro_session->h_entry]);
+	ro_session_lock(ro_session_table, ro_session_entry);
+
+	LM_DBG("event-type=%d", ro_session->event_type);
+
+	switch (ro_session->event_type) {
+	case answered:
+		now = time(0);
+		used_secs = now - ro_session->last_event_timestamp;
+		call_time = now - ro_session->start_time;
+
+		update_stat(billed_secs, used_secs);
+
+		if (ro_session->callid.s != NULL
+				&& ro_session->dlg_h_entry	> 0
+				&& ro_session->dlg_h_id > 0
+				&& ro_session->ro_session_id.s != NULL)
+		{
+			LM_DBG("Found a session to re-apply for timing [%.*s] and user is [%.*s]\n",
+					ro_session->ro_session_id.len,
+					ro_session->ro_session_id.s,
+					ro_session->from_uri.len,
+					ro_session->from_uri.s);
+
+			LM_DBG("Call session has been active for %i seconds. The last reserved secs was [%i] and the last event was [%i seconds] ago",
+					(unsigned int) call_time,
+					(unsigned int) ro_session->reserved_secs,
+					(unsigned int) used_secs);
+
+			LM_DBG("Call session [p=%p]: we will now make a request for another [%i] of credit with a usage of [%i] seconds from the last bundle.\n",
+					ro_session,
+					interim_request_credits/* new reservation request amount */,
+					(unsigned int) used_secs/* charged seconds from previous reservation */);
+
+			// Apply for more credit.
+			//
+			// The function call will return immediately and we will receive the reply asynchronously via a callback
+			send_ccr_interim(ro_session, (unsigned int) used_secs, interim_request_credits);
+			return;
+		}
+		else {
+			LM_ERR("Hmmm, the session we have either doesn't have all the data or something else has gone wrong.\n");
+			/* put the timer back so the call will be killed according to previous timeout. */
+			ro_session->event_type = unknown_error;
+			int ret = insert_ro_timer(&ro_session->ro_tl,
+					ro_session->reserved_secs - used_secs);
+			if (ret != 0) {
+				LM_CRIT("unable to insert timer for Ro Session [%.*s]\n", 
+					ro_session->ro_session_id.len, ro_session->ro_session_id.s); 
+			}
+			else {
+				ref_ro_session_unsafe(ro_session, 1);
+			}
+			LM_ERR("Immediately killing call due to unknown error\n");
+			dlgb.lookup_terminate_dlg(ro_session->dlg_h_entry, ro_session->dlg_h_id, NULL );
+		}
+
+		break;
+	default:
+		LM_ERR("Diameter call session - event [%d]\n", ro_session->event_type);
+
+		if (ro_session->event_type == no_more_credit)
+			LM_INFO("Call/session must be ended - no more funds.\n");
+		else if (ro_session->event_type == unknown_error)
+			LM_ERR("last event caused an error. We will now tear down this session.\n");
+
+		dlgb.lookup_terminate_dlg(ro_session->dlg_h_entry, ro_session->dlg_h_id, NULL );
+	}
+
+	update_stat(killed_calls, 1);
+
+	ro_session_unlock(ro_session_table, ro_session_entry);
+	unref_ro_session(ro_session, 1); //unref from the initial timer that fired this event.
+
+	return;
+}
+

+ 93 - 0
modules/ims_charging/ro_timer.h

@@ -0,0 +1,93 @@
+/* 
+ * File:   ro_timer.h
+ * Author: Jason Penton
+ *
+ * Created on 06 April 2011, 1:39 PM
+ */
+
+#ifndef RO_TIMER_H
+#define	RO_TIMER_H
+
+#include "../../locking.h"
+#include "../../timer.h"
+
+extern struct interim_ccr *i_req;
+
+/*! ro timeout list */
+struct ro_tl {
+    struct ro_tl *next;
+    struct ro_tl *prev;
+    volatile unsigned int timeout; /*!< timeout in seconds */
+};
+
+/*! ro_session timer */
+struct ro_timer {
+    struct ro_tl first; /*!< ro session timeout list */
+    gen_lock_t *lock; /*!< lock for the list */
+};
+
+/*! ro_session timer handler */
+typedef void (*ro_timer_handler)(struct ro_tl *);
+
+
+/*!
+ * \brief Initialize the ro_session timer handler
+ * Initialize the ro_session timer handler, allocate the lock and a global
+ * timer in shared memory. The global timer handler will be set on success.
+ * \param hdl dialog timer handler
+ * \return 0 on success, -1 on failure
+ */
+int init_ro_timer(ro_timer_handler);
+
+
+/*!
+ * \brief Destroy ro_session dialog timer
+ */
+void destroy_ro_timer(void);
+
+
+/*!
+ * \brief Insert a ro_session timer to the list
+ * \param tl ro_session timer list
+ * \param interval timeout value in seconds
+ * \return 0 on success, -1 when the input timer list is invalid
+ */
+int insert_ro_timer(struct ro_tl *tl, int interval);
+
+
+/*!
+ * \brief Remove a ro_session timer from the list
+ * \param tl ro_session timer that should be removed
+ * \return 1 when the input timer is empty, 0 when the timer was removed,
+ * -1 when the input timer list is invalid
+ */
+int remove_ro_timer(struct ro_tl *tl);
+
+
+/*!
+ * \brief Update a ro_session timer on the list
+ * \param tl ro_session timer
+ * \param timeout new timeout value in seconds
+ * \return 0 on success, -1 when the input list is invalid
+ * \note the update is implemented as a remove, insert
+ */
+int update_ro_timer(struct ro_tl *tl, int timeout);
+
+
+/*!
+ * \brief Timer routine for expiration of ro_session credit reservations
+ * Timer handler for expiration of ro_session credit reservations, runs the global timer handler on them.
+ * \param time for expiration checks on credit reservations
+ * \param attr unused
+ */
+void ro_timer_routine(unsigned int ticks, void * attr);
+
+/* this is the function called when a we need to request more funds/credit. We need to try and reserve more credit.
+ * If we cant we need to put a new timer to kill the call at the appropriate time
+ */
+void ro_session_ontimeout(struct ro_tl *tl);
+
+void resume_ro_session_ontimeout(struct interim_ccr *i_req);
+
+#endif	/* RO_TIMER_H */
+

+ 45 - 0
modules/ims_charging/stats.c

@@ -0,0 +1,45 @@
+/*
+ * stats.c
+ *
+ *  Created on: Sep 30, 2013
+ *      Author: carlos
+ */
+
+#include "stats.h"
+
+unsigned long get_failed_initial_ccrs() {
+
+	unsigned long success_number = get_stat_val(successful_initial_ccrs),
+		 total_number	= get_stat_val(initial_ccrs);
+
+	return total_number - success_number;
+}
+
+unsigned long get_failed_interim_ccrs() {
+
+	unsigned long success_number = get_stat_val(successful_interim_ccrs),
+		 total_number	= get_stat_val(interim_ccrs);
+
+	return total_number - success_number;
+}
+
+unsigned long get_failed_final_ccrs() {
+
+	unsigned long success_number = get_stat_val(successful_final_ccrs),
+		 total_number	= get_stat_val(final_ccrs);
+
+	return total_number - success_number;
+}
+
+unsigned long get_ccr_avg_response_time() {
+
+	unsigned long responses_time = get_stat_val(ccr_responses_time),
+				  ccrs	= get_stat_val(initial_ccrs)
+				  	  	  + get_stat_val(interim_ccrs)
+				  	  	  + get_stat_val(final_ccrs);
+
+	if (responses_time == 0 || ccrs == 0)
+		return 0;
+
+	return responses_time / ccrs;
+}

+ 28 - 0
modules/ims_charging/stats.h

@@ -0,0 +1,28 @@
+/*
+ * stats.h
+ *
+ *  Created on: Sep 30, 2013
+ *      Author: carlos
+ */
+
+#ifndef STATS_H_
+#define STATS_H_
+
+#include "../../lib/kcore/statistics.h"
+
+extern stat_var *initial_ccrs;
+extern stat_var *interim_ccrs;
+extern stat_var *final_ccrs;
+extern stat_var *successful_initial_ccrs;
+extern stat_var *successful_interim_ccrs;
+extern stat_var *successful_final_ccrs;
+extern stat_var *ccr_responses_time;
+extern stat_var *billed_secs;
+extern stat_var *killed_calls;
+
+unsigned long get_failed_initial_ccrs();
+unsigned long get_failed_interim_ccrs();
+unsigned long get_failed_final_ccrs();
+unsigned long get_ccr_avg_response_time();
+
+#endif /* STATS_H_ */