summaryrefslogtreecommitdiff
path: root/main/cdr.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/cdr.c')
-rw-r--r--main/cdr.c3942
1 files changed, 2916 insertions, 1026 deletions
diff --git a/main/cdr.c b/main/cdr.c
index a0560676a..9f710fe0d 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -48,6 +48,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <signal.h>
+#include <inttypes.h>
#include "asterisk/lock.h"
#include "asterisk/channel.h"
@@ -62,1143 +63,2900 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/stringfields.h"
#include "asterisk/data.h"
+#include "asterisk/config_options.h"
+#include "asterisk/json.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_message_router.h"
+#include "asterisk/astobj2.h"
/*** DOCUMENTATION
+ <configInfo name="cdr" language="en_US">
+ <synopsis>Call Detail Record configuration</synopsis>
+ <description>
+ <para>CDR is Call Detail Record, which provides logging services via a variety of
+ pluggable backend modules. Detailed call information can be recorded to
+ databases, files, etc. Useful for billing, fraud prevention, compliance with
+ Sarbanes-Oxley aka The Enron Act, QOS evaluations, and more.</para>
+ </description>
+ <configFile name="cdr.conf">
+ <configObject name="general">
+ <synopsis>Global settings applied to the CDR engine.</synopsis>
+ <configOption name="debug">
+ <synopsis>Enable/disable verbose CDR debugging.</synopsis>
+ <description><para>When set to <literal>True</literal>, verbose updates
+ of changes in CDR information will be logged. Note that this is only
+ of use when debugging CDR behavior.</para>
+ </description>
+ </configOption>
+ <configOption name="enable">
+ <synopsis>Enable/disable CDR logging.</synopsis>
+ <description><para>Define whether or not to use CDR logging. Setting this to "no" will override
+ any loading of backend CDR modules. Default is "yes".</para>
+ </description>
+ </configOption>
+ <configOption name="unanswered">
+ <synopsis>Log calls that are never answered.</synopsis>
+ <description><para>Define whether or not to log unanswered calls. Setting this to "yes" will
+ report every attempt to ring a phone in dialing attempts, when it was not
+ answered. For example, if you try to dial 3 extensions, and this option is "yes",
+ you will get 3 CDR's, one for each phone that was rung. Some find this information horribly
+ useless. Others find it very valuable. Note, in "yes" mode, you will see one CDR, with one of
+ the call targets on one side, and the originating channel on the other, and then one CDR for
+ each channel attempted. This may seem redundant, but cannot be helped.</para>
+ <para>In brief, this option controls the reporting of unanswered calls which only have an A
+ party. Calls which get offered to an outgoing line, but are unanswered, are still
+ logged, and that is the intended behavior. (It also results in some B side CDRs being
+ output, as they have the B side channel as their source channel, and no destination
+ channel.)</para>
+ </description>
+ </configOption>
+ <configOption name="congestion">
+ <synopsis>Log congested calls.</synopsis>
+ <description><para>Define whether or not to log congested calls. Setting this to "yes" will
+ report each call that fails to complete due to congestion conditions.</para>
+ </description>
+ </configOption>
+ <configOption name="endbeforehexten">
+ <synopsis>End the CDR before executing the "h" extension</synopsis>
+ <description><para>Normally, CDR's are not closed out until after all extensions are finished
+ executing. By enabling this option, the CDR will be ended before executing
+ the <literal>h</literal> extension and hangup handlers so that CDR values such as <literal>end</literal> and
+ <literal>"billsec"</literal> may be retrieved inside of this extension.
+ The default value is "no".</para>
+ </description>
+ </configOption>
+ <configOption name="initiatedseconds">
+ <synopsis>Count microseconds for billsec purposes</synopsis>
+ <description><para>Normally, the <literal>billsec</literal> field logged to the CDR backends
+ is simply the end time (hangup time) minus the answer time in seconds. Internally,
+ asterisk stores the time in terms of microseconds and seconds. By setting
+ initiatedseconds to <literal>yes</literal>, you can force asterisk to report any seconds
+ that were initiated (a sort of round up method). Technically, this is
+ when the microsecond part of the end time is greater than the microsecond
+ part of the answer time, then the billsec time is incremented one second.</para>
+ </description>
+ </configOption>
+ <configOption name="batch">
+ <synopsis>Submit CDRs to the backends for processing in batches</synopsis>
+ <description><para>Define the CDR batch mode, where instead of posting the CDR at the end of
+ every call, the data will be stored in a buffer to help alleviate load on the
+ asterisk server.</para>
+ <warning><para>Use of batch mode may result in data loss after unsafe asterisk termination,
+ i.e., software crash, power failure, kill -9, etc.</para>
+ </warning>
+ </description>
+ </configOption>
+ <configOption name="size">
+ <synopsis>The maximum number of CDRs to accumulate before triggering a batch</synopsis>
+ <description><para>Define the maximum number of CDRs to accumulate in the buffer before posting
+ them to the backend engines. batch must be set to <literal>yes</literal>.</para>
+ </description>
+ </configOption>
+ <configOption name="time">
+ <synopsis>The maximum time to accumulate CDRs before triggering a batch</synopsis>
+ <description><para>Define the maximum time to accumulate CDRs before posting them in a batch to the
+ backend engines. If this time limit is reached, then it will post the records, regardless of the value
+ defined for size. batch must be set to <literal>yes</literal>.</para>
+ <note><para>Time is expressed in seconds.</para></note>
+ </description>
+ </configOption>
+ <configOption name="scheduleronly">
+ <synopsis>Post batched CDRs on their own thread instead of the scheduler</synopsis>
+ <description><para>The CDR engine uses the internal asterisk scheduler to determine when to post
+ records. Posting can either occur inside the scheduler thread, or a new
+ thread can be spawned for the submission of every batch. For small batches,
+ it might be acceptable to just use the scheduler thread, so set this to <literal>yes</literal>.
+ For large batches, say anything over size=10, a new thread is recommended, so
+ set this to <literal>no</literal>.</para>
+ </description>
+ </configOption>
+ <configOption name="safeshutdown">
+ <synopsis>Block shutdown of Asterisk until CDRs are submitted</synopsis>
+ <description><para>When shutting down asterisk, you can block until the CDRs are submitted. If
+ you don't, then data will likely be lost. You can always check the size of
+ the CDR batch buffer with the CLI <astcli>cdr status</astcli> command. To enable blocking on
+ submission of CDR data during asterisk shutdown, set this to <literal>yes</literal>.</para>
+ </description>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
***/
-/*! Default AMA flag for billing records (CDR's) */
-int ast_default_amaflags = AST_CDR_DOCUMENTATION;
-char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
-struct ast_cdr_beitem {
+#define DEFAULT_ENABLED "1"
+#define DEFAULT_BATCHMODE "0"
+#define DEFAULT_UNANSWERED "0"
+#define DEFAULT_CONGESTION "0"
+#define DEFAULT_END_BEFORE_H_EXTEN "0"
+#define DEFAULT_INITIATED_SECONDS "0"
+
+#define DEFAULT_BATCH_SIZE "100"
+#define MAX_BATCH_SIZE 1000
+#define DEFAULT_BATCH_TIME "300"
+#define MAX_BATCH_TIME 86400
+#define DEFAULT_BATCH_SCHEDULER_ONLY "0"
+#define DEFAULT_BATCH_SAFE_SHUTDOWN "1"
+
+#define CDR_DEBUG(mod_cfg, fmt, ...) \
+ do { \
+ if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
+ ast_verb(1, (fmt), ##__VA_ARGS__); \
+ } } while (0)
+
+static void cdr_detach(struct ast_cdr *cdr);
+static void cdr_submit_batch(int shutdown);
+
+/*! \brief The configuration settings for this module */
+struct module_config {
+ struct ast_cdr_config *general; /*< CDR global settings */
+};
+
+/*! \brief The container for the module configuration */
+static AO2_GLOBAL_OBJ_STATIC(module_configs);
+
+/*! \brief The type definition for general options */
+static struct aco_type general_option = {
+ .type = ACO_GLOBAL,
+ .name = "general",
+ .item_offset = offsetof(struct module_config, general),
+ .category = "^general$",
+ .category_match = ACO_WHITELIST,
+};
+
+static void *module_config_alloc(void);
+static void module_config_destructor(void *obj);
+
+/*! \brief The file definition */
+static struct aco_file module_file_conf = {
+ .filename = "cdr.conf",
+ .skip_category = "(^csv$|^custom$|^manager$|^odbc$|^pgsql$|^radius$|^sqlite$|^tds$|^mysql$)",
+ .types = ACO_TYPES(&general_option),
+};
+
+CONFIG_INFO_CORE("cdr", cfg_info, module_configs, module_config_alloc,
+ .files = ACO_FILES(&module_file_conf),
+);
+
+static struct aco_type *general_options[] = ACO_TYPES(&general_option);
+
+/*! \brief Dispose of a module config object */
+static void module_config_destructor(void *obj)
+{
+ struct module_config *cfg = obj;
+
+ if (!cfg) {
+ return;
+ }
+ ao2_ref(cfg->general, -1);
+}
+
+/*! \brief Create a new module config object */
+static void *module_config_alloc(void)
+{
+ struct module_config *mod_cfg;
+ struct ast_cdr_config *cdr_config;
+
+ mod_cfg = ao2_alloc(sizeof(*mod_cfg), module_config_destructor);
+ if (!mod_cfg) {
+ return NULL;
+ }
+
+ cdr_config = ao2_alloc(sizeof(*cdr_config), NULL);
+ if (!cdr_config) {
+ ao2_ref(cdr_config, -1);
+ return NULL;
+ }
+ mod_cfg->general = cdr_config;
+
+ return mod_cfg;
+}
+
+/*! \brief Registration object for CDR backends */
+struct cdr_beitem {
char name[20];
char desc[80];
ast_cdrbe be;
- AST_RWLIST_ENTRY(ast_cdr_beitem) list;
+ AST_RWLIST_ENTRY(cdr_beitem) list;
};
-static AST_RWLIST_HEAD_STATIC(be_list, ast_cdr_beitem);
+/*! \brief List of registered backends */
+static AST_RWLIST_HEAD_STATIC(be_list, cdr_beitem);
-struct ast_cdr_batch_item {
+/*! \brief Queued CDR waiting to be batched */
+struct cdr_batch_item {
struct ast_cdr *cdr;
- struct ast_cdr_batch_item *next;
+ struct cdr_batch_item *next;
};
-static struct ast_cdr_batch {
+/*! \brief The actual batch queue */
+static struct cdr_batch {
int size;
- struct ast_cdr_batch_item *head;
- struct ast_cdr_batch_item *tail;
+ struct cdr_batch_item *head;
+ struct cdr_batch_item *tail;
} *batch = NULL;
+/*! \brief The global sequence counter used for CDRs */
+static int global_cdr_sequence = 0;
-static int cdr_sequence = 0;
-
-static int cdr_seq_inc(struct ast_cdr *cdr);
-
+/*! \brief Scheduler items */
static struct ast_sched_context *sched;
static int cdr_sched = -1;
+AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
static pthread_t cdr_thread = AST_PTHREADT_NULL;
-static int enabled;
-static const int ENABLED_DEFAULT = 1;
+/*! \brief Lock protecting modifications to the batch queue */
+AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
-static int batchmode;
-static const int BATCHMODE_DEFAULT = 0;
+/*! \brief These are used to wake up the CDR thread when there's work to do */
+AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
+static ast_cond_t cdr_pending_cond;
-static int unanswered;
-static const int UNANSWERED_DEFAULT = 0;
+/*! \brief A container of the active CDRs indexed by Party A channel name */
+static struct ao2_container *active_cdrs_by_channel;
-static int congestion;
-static const int CONGESTION_DEFAULT = 0;
+/*! \brief A container of the active CDRs indexed by the bridge ID */
+static struct ao2_container *active_cdrs_by_bridge;
-static int batchsize;
-static const int BATCH_SIZE_DEFAULT = 100;
+/*! \brief Message router for stasis messages regarding channel state */
+static struct stasis_message_router *stasis_router;
-static int batchtime;
-static const int BATCH_TIME_DEFAULT = 300;
+/*! \brief Our subscription for bridges */
+static struct stasis_subscription *bridge_subscription;
-static int batchscheduleronly;
-static const int BATCH_SCHEDULER_ONLY_DEFAULT = 0;
+/*! \brief Our subscription for channels */
+static struct stasis_subscription *channel_subscription;
-static int batchsafeshutdown;
-static const int BATCH_SAFE_SHUTDOWN_DEFAULT = 1;
+/*! \brief The parent topic for all topics we want to aggregate for CDRs */
+static struct stasis_topic *cdr_topic;
-AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
+struct cdr_object;
-AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
+/*!
+ * \brief A virtual table used for \ref cdr_object.
+ *
+ * Note that all functions are optional - if a subclass does not need an
+ * implementation, it is safe to leave it NULL.
+ */
+struct cdr_object_fn_table {
+ /*! \brief Name of the subclass */
+ const char *name;
+
+ /*!
+ * \brief An initialization function. This will be called automatically
+ * when a \ref cdr_object is switched to this type in
+ * \ref cdr_object_transition_state
+ *
+ * \param cdr The \ref cdr_object that was just transitioned
+ */
+ void (* const init_function)(struct cdr_object *cdr);
+
+ /*!
+ * \brief Process a Party A update for the \ref cdr_object
+ *
+ * \param cdr The \ref cdr_object to process the update
+ * \param snapshot The snapshot for the CDR's Party A
+ * \retval 0 the CDR handled the update or ignored it
+ * \retval 1 the CDR is finalized and a new one should be made to handle it
+ */
+ int (* const process_party_a)(struct cdr_object *cdr,
+ struct ast_channel_snapshot *snapshot);
+
+ /*!
+ * \brief Process a Party B update for the \ref cdr_object
+ *
+ * \param cdr The \ref cdr_object to process the update
+ * \param snapshot The snapshot for the CDR's Party B
+ */
+ void (* const process_party_b)(struct cdr_object *cdr,
+ struct ast_channel_snapshot *snapshot);
+
+ /*!
+ * \brief Process the beginning of a dial. A dial message implies one of two
+ * things:
+ * The \ref cdr_object's Party A has been originated
+ * The \ref cdr_object's Party A is dialing its Party B
+ *
+ * \param cdr The \ref cdr_object
+ * \param caller The originator of the dial attempt
+ * \param peer The destination of the dial attempt
+ *
+ * \retval 0 if the parties in the dial were handled by this CDR
+ * \retval 1 if the parties could not be handled by this CDR
+ */
+ int (* const process_dial_begin)(struct cdr_object *cdr,
+ struct ast_channel_snapshot *caller,
+ struct ast_channel_snapshot *peer);
+
+ /*!
+ * \brief Process the end of a dial. At the end of a dial, a CDR can be
+ * transitioned into one of two states - DialedPending
+ * (\ref dialed_pending_state_fn_table) or Finalized
+ * (\ref finalized_state_fn_table).
+ *
+ * \param cdr The \ref cdr_object
+ * \param caller The originator of the dial attempt
+ * \param peer the Destination of the dial attempt
+ * \param dial_status What happened
+ *
+ * \retval 0 if the parties in the dial were handled by this CDR
+ * \retval 1 if the parties could not be handled by this CDR
+ */
+ int (* const process_dial_end)(struct cdr_object *cdr,
+ struct ast_channel_snapshot *caller,
+ struct ast_channel_snapshot *peer,
+ const char *dial_status);
+
+ /*!
+ * \brief Process the entering of a bridge by this CDR. The purpose of this
+ * callback is to have the CDR prepare itself for the bridge and attempt to
+ * find a valid Party B. The act of creating new CDRs based on the entering
+ * of this channel into the bridge is handled by the higher level message
+ * handler.
+ *
+ * \param cdr The \ref cdr_object
+ * \param bridge The bridge that the Party A just entered into
+ * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+ *
+ * \retval 0 This CDR found a Party B for itself and updated it, or there
+ * was no Party B to find (we're all alone)
+ * \retval 1 This CDR couldn't find a Party B, and there were options
+ */
+ int (* const process_bridge_enter)(struct cdr_object *cdr,
+ struct ast_bridge_snapshot *bridge,
+ struct ast_channel_snapshot *channel);
+
+ /*!
+ * \brief Process the leaving of a bridge by this CDR.
+ *
+ * \param cdr The \ref cdr_object
+ * \param bridge The bridge that the Party A just left
+ * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+ *
+ * \retval 0 This CDR left successfully
+ * \retval 1 Error
+ */
+ int (* const process_bridge_leave)(struct cdr_object *cdr,
+ struct ast_bridge_snapshot *bridge,
+ struct ast_channel_snapshot *channel);
+};
-/* these are used to wake up the CDR thread when there's work to do */
-AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
-static ast_cond_t cdr_pending_cond;
+static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
-int check_cdr_enabled(void)
-{
- return enabled;
-}
+static void single_state_init_function(struct cdr_object *cdr);
+static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
/*!
- * \brief Register a CDR driver. Each registered CDR driver generates a CDR
- * \retval 0 on success.
- * \retval -1 on error
+ * \brief The virtual table for the Single state.
+ *
+ * A \ref cdr_object starts off in this state. This represents a channel that
+ * has no Party B information itself.
+ *
+ * A \ref cdr_object from this state can go into any of the following states:
+ * * \ref dial_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
*/
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
-{
- struct ast_cdr_beitem *i = NULL;
+struct cdr_object_fn_table single_state_fn_table = {
+ .name = "Single",
+ .init_function = single_state_init_function,
+ .process_party_a = base_process_party_a,
+ .process_party_b = single_state_process_party_b,
+ .process_dial_begin = single_state_process_dial_begin,
+ .process_dial_end = base_process_dial_end,
+ .process_bridge_enter = single_state_process_bridge_enter,
+ .process_bridge_leave = base_process_bridge_leave,
+};
- if (!name)
- return -1;
+static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
- if (!be) {
- ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
- return -1;
- }
+/*!
+ * \brief The virtual table for the Dial state.
+ *
+ * A \ref cdr_object that has begun a dial operation. This state is entered when
+ * the Party A for a CDR is determined to be dialing out to a Party B or when
+ * a CDR is for an originated channel (in which case the Party A information is
+ * the originated channel, and there is no Party B).
+ *
+ * A \ref cdr_object from this state can go in any of the following states:
+ * * \ref dialed_pending_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
+ */
+struct cdr_object_fn_table dial_state_fn_table = {
+ .name = "Dial",
+ .process_party_a = base_process_party_a,
+ .process_party_b = dial_state_process_party_b,
+ .process_dial_begin = dial_state_process_dial_begin,
+ .process_dial_end = dial_state_process_dial_end,
+ .process_bridge_enter = dial_state_process_bridge_enter,
+ .process_bridge_leave = base_process_bridge_leave,
+};
- AST_RWLIST_WRLOCK(&be_list);
- AST_RWLIST_TRAVERSE(&be_list, i, list) {
- if (!strcasecmp(name, i->name)) {
- ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
- AST_RWLIST_UNLOCK(&be_list);
- return -1;
- }
- }
+static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
- if (!(i = ast_calloc(1, sizeof(*i))))
- return -1;
+/*!
+ * \brief The virtual table for the Dialed Pending state.
+ *
+ * A \ref cdr_object that has successfully finished a dial operation, but we
+ * don't know what they're going to do yet. It's theoretically possible to dial
+ * a party and then have that party not be bridged with the caller; likewise,
+ * an origination can complete and the channel go off and execute dialplan. The
+ * pending state acts as a bridge between either:
+ * * Entering a bridge
+ * * Getting a new CDR for new dialplan execution
+ * * Switching from being originated to executing dialplan
+ *
+ * A \ref cdr_object from this state can go in any of the following states:
+ * * \ref single_state_fn_table
+ * * \ref dialed_pending_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
+ */
+struct cdr_object_fn_table dialed_pending_state_fn_table = {
+ .name = "DialedPending",
+ .process_party_a = dialed_pending_state_process_party_a,
+ .process_dial_begin = dialed_pending_state_process_dial_begin,
+ .process_bridge_enter = dialed_pending_state_process_bridge_enter,
+ .process_bridge_leave = base_process_bridge_leave,
+};
- i->be = be;
- ast_copy_string(i->name, name, sizeof(i->name));
- ast_copy_string(i->desc, desc, sizeof(i->desc));
+static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
- AST_RWLIST_INSERT_HEAD(&be_list, i, list);
- AST_RWLIST_UNLOCK(&be_list);
+/*!
+ * \brief The virtual table for the Bridged state
+ *
+ * A \ref cdr_object enters this state when it receives notification that the
+ * channel has entered a bridge.
+ *
+ * A \ref cdr_object from this state can go to:
+ * * \ref finalized_state_fn_table
+ * * \ref pending_state_fn_table
+ */
+struct cdr_object_fn_table bridge_state_fn_table = {
+ .name = "Bridged",
+ .process_party_a = base_process_party_a,
+ .process_party_b = bridge_state_process_party_b,
+ .process_bridge_leave = bridge_state_process_bridge_leave,
+};
- return 0;
-}
+static void pending_state_init_function(struct cdr_object *cdr);
+static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
-/*! unregister a CDR driver */
-void ast_cdr_unregister(const char *name)
+/*!
+ * \brief The virtual table for the Pending state
+ *
+ * At certain times, we don't know where to go with the CDR. A good example is
+ * when a channel leaves a bridge - we don't know if the channel is about to
+ * be hung up; if it is about to go execute dialplan; dial someone; go into
+ * another bridge, etc. At these times, the CDR goes into pending and observes
+ * the messages that come in next to infer where the next logical place to go
+ * is.
+ *
+ * In this state, a CDR can go anywhere!
+ */
+struct cdr_object_fn_table bridged_pending_state_fn_table = {
+ .name = "Pending",
+ .init_function = pending_state_init_function,
+ .process_party_a = pending_state_process_party_a,
+ .process_dial_begin = pending_state_process_dial_begin,
+ .process_dial_end = base_process_dial_end,
+ .process_bridge_enter = pending_state_process_bridge_enter,
+ .process_bridge_leave = base_process_bridge_leave,
+};
+
+static void finalized_state_init_function(struct cdr_object *cdr);
+static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+
+/*!
+ * \brief The virtual table for the finalized state.
+ *
+ * Once in the finalized state, the CDR is done. No modifications can be made
+ * to the CDR.
+ */
+struct cdr_object_fn_table finalized_state_fn_table = {
+ .name = "Finalized",
+ .init_function = finalized_state_init_function,
+ .process_party_a = finalized_state_process_party_a,
+};
+
+/*! \brief A wrapper object around a snapshot.
+ * Fields that are mutable by the CDR engine are replicated here.
+ */
+struct cdr_object_snapshot {
+ struct ast_channel_snapshot *snapshot; /*!< The channel snapshot */
+ char userfield[AST_MAX_USER_FIELD]; /*!< Userfield for the channel */
+ unsigned int flags; /*!< Specific flags for this party */
+ struct varshead variables; /*!< CDR variables for the channel */
+};
+
+/*! \brief An in-memory representation of an active CDR */
+struct cdr_object {
+ struct cdr_object_snapshot party_a; /*!< The Party A information */
+ struct cdr_object_snapshot party_b; /*!< The Party B information */
+ struct cdr_object_fn_table *fn_table; /*!< The current virtual table */
+
+ enum ast_cdr_disposition disposition; /*!< The disposition of the CDR */
+ struct timeval start; /*!< When this CDR was created */
+ struct timeval answer; /*!< Either when the channel was answered, or when the path between channels was established */
+ struct timeval end; /*!< When this CDR was finalized */
+ unsigned int sequence; /*!< A monotonically increasing number for each CDR */
+ struct ast_flags flags; /*!< Flags on the CDR */
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(linkedid); /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
+ AST_STRING_FIELD(name); /*!< Channel name of party A. Cached here as the party A address may change */
+ AST_STRING_FIELD(bridge); /*!< The bridge the party A happens to be in. */
+ AST_STRING_FIELD(appl); /*!< The last accepted application party A was in */
+ AST_STRING_FIELD(data); /*!< The data for the last accepted application party A was in */
+ );
+ struct cdr_object *next; /*!< The next CDR object in the chain */
+ struct cdr_object *last; /*!< The last CDR object in the chain */
+};
+
+/*!
+ * \brief Copy variables from one list to another
+ * \param to_list destination
+ * \param from_list source
+ * \retval The number of copied variables
+ */
+static int copy_variables(struct varshead *to_list, struct varshead *from_list)
{
- struct ast_cdr_beitem *i = NULL;
+ struct ast_var_t *variables, *newvariable = NULL;
+ const char *var, *val;
+ int x = 0;
- AST_RWLIST_WRLOCK(&be_list);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
- if (!strcasecmp(name, i->name)) {
- AST_RWLIST_REMOVE_CURRENT(list);
- break;
+ AST_LIST_TRAVERSE(from_list, variables, entries) {
+ if (variables &&
+ (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
+ !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
+ newvariable = ast_var_assign(var, val);
+ AST_LIST_INSERT_HEAD(to_list, newvariable, entries);
+ x++;
}
}
- AST_RWLIST_TRAVERSE_SAFE_END;
- AST_RWLIST_UNLOCK(&be_list);
- if (i) {
- ast_verb(2, "Unregistered '%s' CDR backend\n", name);
- ast_free(i);
+ return x;
+}
+
+/*!
+ * \brief Delete all variables from a variable list
+ * \param headp The head pointer to the variable list to delete
+ */
+static void free_variables(struct varshead *headp)
+{
+ struct ast_var_t *vardata;
+
+ while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+ ast_var_delete(vardata);
}
}
-int ast_cdr_isset_unanswered(void)
+/*!
+ * \brief Copy a snapshot and its details
+ * \param dst The destination
+ * \param src The source
+ */
+static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr_object_snapshot *src)
{
- return unanswered;
+ if (dst->snapshot) {
+ ao2_t_ref(dst->snapshot, -1, "release old snapshot during copy");
+ }
+ dst->snapshot = src->snapshot;
+ ao2_t_ref(dst->snapshot, +1, "bump new snapshot during copy");
+ strcpy(dst->userfield, src->userfield);
+ dst->flags = src->flags;
+ copy_variables(&dst->variables, &src->variables);
}
-int ast_cdr_isset_congestion(void)
+/*!
+ * \brief Transition a \ref cdr_object to a new state
+ * \param cdr The \ref cdr_object to transition
+ * \param fn_table The \ref cdr_object_fn_table state to go to
+ */
+static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ CDR_DEBUG(mod_cfg, "%p - Transitioning CDR for %s from state %s to %s\n",
+ cdr, cdr->party_a.snapshot->name,
+ cdr->fn_table ? cdr->fn_table->name : "NONE", fn_table->name);
+ cdr->fn_table = fn_table;
+ if (cdr->fn_table->init_function) {
+ cdr->fn_table->init_function(cdr);
+ }
+}
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by Party A name */
+static int cdr_object_channel_hash_fn(const void *obj, const int flags)
{
- return congestion;
+ const struct cdr_object *cdr = obj;
+ const char *name = (flags & OBJ_KEY) ? obj : cdr->name;
+ return ast_str_case_hash(name);
}
-struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr)
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by Party A name
+ */
+static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
{
- struct ast_cdr *newcdr = ast_cdr_dup(cdr);
- if (!newcdr)
- return NULL;
+ struct cdr_object *left = obj;
+ struct cdr_object *right = arg;
+ const char *match = (flags & OBJ_KEY) ? arg : right->name;
+ return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
+}
- cdr_seq_inc(newcdr);
- return newcdr;
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by bridge ID
+ */
+static int cdr_object_bridge_hash_fn(const void *obj, const int flags)
+{
+ const struct cdr_object *cdr = obj;
+ const char *id = (flags & OBJ_KEY) ? obj : cdr->bridge;
+ return ast_str_case_hash(id);
}
-struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr)
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by bridge. Note
+ * that we expect there to be collisions, as a single bridge may have multiple
+ * CDRs active at one point in time
+ */
+static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct cdr_object *left = obj;
+ struct cdr_object *right = arg;
+ struct cdr_object *it_cdr;
+ const char *match = (flags & OBJ_KEY) ? arg : right->bridge;
+ for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) {
+ if (!strcasecmp(it_cdr->bridge, match)) {
+ return CMP_MATCH;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * \brief \ref cdr_object Destructor
+ */
+static void cdr_object_dtor(void *obj)
{
- struct ast_cdr *newcdr = ast_cdr_dup(cdr);
- if (!newcdr)
- return NULL;
+ struct cdr_object *cdr = obj;
+ struct ast_var_t *it_var;
- cdr_seq_inc(cdr);
- return newcdr;
+ if (!cdr) {
+ return;
+ }
+
+ ao2_cleanup(cdr->party_a.snapshot);
+ ao2_cleanup(cdr->party_b.snapshot);
+ while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) {
+ ast_var_delete(it_var);
+ }
+ while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b.variables, entries))) {
+ ast_var_delete(it_var);
+ }
+ ast_string_field_free_memory(cdr);
+
+ if (cdr->next) {
+ ao2_cleanup(cdr->next);
+ }
}
-/*! Duplicate a CDR record
- \returns Pointer to new CDR record
-*/
-struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
+/*!
+ * \brief \ref cdr_object constructor
+ * \param chan The \ref ast_channel_snapshot that is the CDR's Party A
+ *
+ * This implicitly sets the state of the newly created CDR to the Single state
+ * (\ref single_state_fn_table)
+ */
+static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
{
- struct ast_cdr *newcdr;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct cdr_object *cdr;
+
+ ast_assert(chan != NULL);
- if (!cdr) /* don't die if we get a null cdr pointer */
+ cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor);
+ if (!cdr) {
return NULL;
- newcdr = ast_cdr_alloc();
- if (!newcdr)
+ }
+ cdr->last = cdr;
+ if (ast_string_field_init(cdr, 64)) {
return NULL;
+ }
+ ast_string_field_set(cdr, name, chan->name);
+ ast_string_field_set(cdr, linkedid, chan->linkedid);
+ cdr->disposition = AST_CDR_NULL;
+ cdr->sequence = ast_atomic_fetchadd_int(&global_cdr_sequence, +1);
- memcpy(newcdr, cdr, sizeof(*newcdr));
- /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
- memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
- ast_cdr_copy_vars(newcdr, cdr);
- newcdr->next = NULL;
+ cdr->party_a.snapshot = chan;
+ ao2_t_ref(cdr->party_a.snapshot, +1, "bump snapshot during CDR creation");
- return newcdr;
+ CDR_DEBUG(mod_cfg, "%p - Created CDR for channel %s\n", cdr, chan->name);
+
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+
+ return cdr;
}
-static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur)
+/*!
+ * \brief Create a new \ref cdr_object and append it to an existing chain
+ * \param cdr The \ref cdr_object to append to
+ */
+static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
{
- if (ast_strlen_zero(name))
+ struct cdr_object *new_cdr;
+ struct cdr_object *it_cdr;
+ struct cdr_object *cdr_last;
+
+ cdr_last = cdr->last;
+ new_cdr = cdr_object_alloc(cdr_last->party_a.snapshot);
+ if (!new_cdr) {
return NULL;
+ }
+ new_cdr->disposition = AST_CDR_NULL;
- for (; cdr; cdr = recur ? cdr->next : NULL) {
- struct ast_var_t *variables;
- struct varshead *headp = &cdr->varshead;
- AST_LIST_TRAVERSE(headp, variables, entries) {
- if (!strcasecmp(name, ast_var_name(variables)))
- return ast_var_value(variables);
- }
+ /* Copy over the linkedid, as it may have changed */
+ ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid);
+ ast_string_field_set(new_cdr, appl, cdr_last->appl);
+ ast_string_field_set(new_cdr, data, cdr_last->data);
+
+ /* Copy over other Party A information */
+ cdr_object_snapshot_copy(&new_cdr->party_a, &cdr_last->party_a);
+
+ /* Append the CDR to the end of the list */
+ for (it_cdr = cdr; it_cdr->next; it_cdr = it_cdr->next) {
+ it_cdr->last = new_cdr;
}
+ it_cdr->last = new_cdr;
+ it_cdr->next = new_cdr;
- return NULL;
+ return new_cdr;
}
-static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
+/*!
+ * \brief Return whether or not a \ref ast_channel_snapshot is for a channel
+ * that was created as the result of a dial operation
+ *
+ * \retval 0 the channel was not created as the result of a dial
+ * \retval 1 the channel was created as the result of a dial
+ */
+static int snapshot_is_dialed(struct ast_channel_snapshot *snapshot)
{
- if (fmt == NULL) { /* raw mode */
- snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
+ return (ast_test_flag(&snapshot->flags, AST_FLAG_OUTGOING)
+ && !(ast_test_flag(&snapshot->flags, AST_FLAG_ORIGINATED)));
+}
+
+/*!
+ * \brief Given two CDR snapshots, figure out who should be Party A for the
+ * resulting CDR
+ * \param left One of the snapshots
+ * \param right The other snapshot
+ * \retval The snapshot that won
+ */
+static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_snapshot *left, struct cdr_object_snapshot *right)
+{
+ /* Check whether or not the party is dialed. A dialed party is never the
+ * Party A with a party that was not dialed.
+ */
+ if (!snapshot_is_dialed(left->snapshot) && snapshot_is_dialed(right->snapshot)) {
+ return left;
+ } else if (snapshot_is_dialed(left->snapshot) && !snapshot_is_dialed(right->snapshot)) {
+ return right;
+ }
+
+ /* Try the Party A flag */
+ if (ast_test_flag(left, AST_CDR_FLAG_PARTY_A) && !ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) {
+ return left;
+ } else if (!ast_test_flag(right, AST_CDR_FLAG_PARTY_A) && ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) {
+ return right;
+ }
+
+ /* Neither party is dialed and neither has the Party A flag - defer to
+ * creation time */
+ if (left->snapshot->creationtime.tv_sec < right->snapshot->creationtime.tv_sec) {
+ return left;
+ } else if (left->snapshot->creationtime.tv_sec > right->snapshot->creationtime.tv_sec) {
+ return right;
+ } else if (left->snapshot->creationtime.tv_usec > right->snapshot->creationtime.tv_usec) {
+ return right;
} else {
- if (when.tv_sec) {
- struct ast_tm tm;
+ /* Okay, fine, take the left one */
+ return left;
+ }
+}
- ast_localtime(&when, &tm, NULL);
- ast_strftime(buf, bufsize, fmt, &tm);
- }
+/*!
+ * Compute the duration for a \ref cdr_object
+ */
+static long cdr_object_get_duration(struct cdr_object *cdr)
+{
+ if (ast_tvzero(cdr->end)) {
+ return (long)(ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
+ } else {
+ return (long)(ast_tvdiff_ms(cdr->end, cdr->start) / 1000);
}
}
-/*! CDR channel variable retrieval */
-void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw)
+/*!
+ * \brief Compute the billsec for a \ref cdr_object
+ */
+static long cdr_object_get_billsec(struct cdr_object *cdr)
{
- const char *fmt = "%Y-%m-%d %T";
- const char *varbuf;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ long int ms;
- if (!cdr) /* don't die if the cdr is null */
- return;
+ if (ast_tvzero(cdr->answer)) {
+ return 0;
+ }
+ ms = ast_tvdiff_ms(cdr->end, cdr->answer);
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS)
+ && (ms % 1000 >= 500)) {
+ ms = (ms / 1000) + 1;
+ } else {
+ ms = ms / 1000;
+ }
- *ret = NULL;
- /* special vars (the ones from the struct ast_cdr when requested by name)
- I'd almost say we should convert all the stringed vals to vars */
+ return ms;
+}
- if (!strcasecmp(name, "clid"))
- ast_copy_string(workspace, cdr->clid, workspacelen);
- else if (!strcasecmp(name, "src"))
- ast_copy_string(workspace, cdr->src, workspacelen);
- else if (!strcasecmp(name, "dst"))
- ast_copy_string(workspace, cdr->dst, workspacelen);
- else if (!strcasecmp(name, "dcontext"))
- ast_copy_string(workspace, cdr->dcontext, workspacelen);
- else if (!strcasecmp(name, "channel"))
- ast_copy_string(workspace, cdr->channel, workspacelen);
- else if (!strcasecmp(name, "dstchannel"))
- ast_copy_string(workspace, cdr->dstchannel, workspacelen);
- else if (!strcasecmp(name, "lastapp"))
- ast_copy_string(workspace, cdr->lastapp, workspacelen);
- else if (!strcasecmp(name, "lastdata"))
- ast_copy_string(workspace, cdr->lastdata, workspacelen);
- else if (!strcasecmp(name, "start"))
- cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
- else if (!strcasecmp(name, "answer"))
- cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
- else if (!strcasecmp(name, "end"))
- cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
- else if (!strcasecmp(name, "duration")) {
- snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
- } else if (!strcasecmp(name, "billsec")) {
- snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);
- } else if (!strcasecmp(name, "disposition")) {
- if (raw) {
- snprintf(workspace, workspacelen, "%ld", cdr->disposition);
- } else {
- ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
+/*!
+ * \brief Create a chain of \ref ast_cdr objects from a chain of \ref cdr_object
+ * suitable for consumption by the registered CDR backends
+ * \param cdr The \ref cdr_object to convert to a public record
+ * \retval A chain of \ref ast_cdr objects on success
+ * \retval NULL on failure
+ */
+static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
+{
+ struct ast_cdr *pub_cdr = NULL, *cdr_prev;
+ struct ast_var_t *it_var, *it_copy_var;
+ struct ast_channel_snapshot *party_a;
+ struct ast_channel_snapshot *party_b;
+
+ while (cdr) {
+ struct ast_cdr *cdr_copy;
+
+ /* Don't create records for CDRs where the party A was a dialed channel */
+ if (snapshot_is_dialed(cdr->party_a.snapshot)) {
+ cdr = cdr->next;
+ continue;
}
- } else if (!strcasecmp(name, "amaflags")) {
- if (raw) {
- snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
+
+ cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+ if (!cdr_copy) {
+ ast_free(pub_cdr);
+ return NULL;
+ }
+
+ party_a = cdr->party_a.snapshot;
+ party_b = cdr->party_b.snapshot;
+
+ /* Party A */
+ ast_assert(party_a != NULL);
+ ast_copy_string(cdr_copy->accountcode, party_a->accountcode, sizeof(cdr_copy->accountcode));
+ cdr_copy->amaflags = party_a->amaflags;
+ ast_copy_string(cdr_copy->channel, party_a->name, sizeof(cdr_copy->channel));
+ ast_callerid_merge(cdr_copy->clid, sizeof(cdr_copy->clid), party_a->caller_name, party_a->caller_number, "");
+ ast_copy_string(cdr_copy->src, party_a->caller_number, sizeof(cdr_copy->src));
+ ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid));
+ ast_copy_string(cdr_copy->lastapp, cdr->appl, sizeof(cdr_copy->lastapp));
+ ast_copy_string(cdr_copy->lastdata, cdr->data, sizeof(cdr_copy->lastdata));
+ ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst));
+ ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext));
+
+ /* Party B */
+ if (party_b) {
+ ast_copy_string(cdr_copy->dstchannel, party_b->name, sizeof(cdr_copy->dstchannel));
+ ast_copy_string(cdr_copy->peeraccount, party_b->accountcode, sizeof(cdr_copy->peeraccount));
+ if (!ast_strlen_zero(cdr->party_b.userfield)) {
+ snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", cdr->party_a.userfield, cdr->party_b.userfield);
+ }
+ }
+ if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(cdr->party_a.userfield)) {
+ ast_copy_string(cdr_copy->userfield, cdr->party_a.userfield, sizeof(cdr_copy->userfield));
+ }
+
+ /* Timestamps/durations */
+ cdr_copy->start = cdr->start;
+ cdr_copy->answer = cdr->answer;
+ cdr_copy->end = cdr->end;
+ cdr_copy->billsec = cdr_object_get_billsec(cdr);
+ cdr_copy->duration = cdr_object_get_duration(cdr);
+
+ /* Flags and IDs */
+ ast_copy_flags(cdr_copy, &cdr->flags, AST_FLAGS_ALL);
+ ast_copy_string(cdr_copy->linkedid, cdr->linkedid, sizeof(cdr_copy->linkedid));
+ cdr_copy->disposition = cdr->disposition;
+ cdr_copy->sequence = cdr->sequence;
+
+ /* Variables */
+ copy_variables(&cdr_copy->varshead, &cdr->party_a.variables);
+ AST_LIST_TRAVERSE(&cdr->party_b.variables, it_var, entries) {
+ int found = 0;
+ AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
+ if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ AST_LIST_INSERT_TAIL(&cdr_copy->varshead, ast_var_assign(ast_var_name(it_var),
+ ast_var_value(it_var)), entries);
+ }
+ }
+
+ if (!pub_cdr) {
+ pub_cdr = cdr_copy;
+ cdr_prev = pub_cdr;
} else {
- ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen);
+ cdr_prev->next = cdr_copy;
+ cdr_prev = cdr_copy;
}
- } else if (!strcasecmp(name, "accountcode"))
- ast_copy_string(workspace, cdr->accountcode, workspacelen);
- else if (!strcasecmp(name, "peeraccount"))
- ast_copy_string(workspace, cdr->peeraccount, workspacelen);
- else if (!strcasecmp(name, "uniqueid"))
- ast_copy_string(workspace, cdr->uniqueid, workspacelen);
- else if (!strcasecmp(name, "linkedid"))
- ast_copy_string(workspace, cdr->linkedid, workspacelen);
- else if (!strcasecmp(name, "userfield"))
- ast_copy_string(workspace, cdr->userfield, workspacelen);
- else if (!strcasecmp(name, "sequence"))
- snprintf(workspace, workspacelen, "%d", cdr->sequence);
- else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur)))
- ast_copy_string(workspace, varbuf, workspacelen);
- else
- workspace[0] = '\0';
+ cdr = cdr->next;
+ }
- if (!ast_strlen_zero(workspace))
- *ret = workspace;
+ return pub_cdr;
}
-/* readonly cdr variables */
-static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
- "lastapp", "lastdata", "start", "answer", "end", "duration",
- "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
- "userfield", "sequence", NULL };
-/*! Set a CDR channel variable
- \note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
-*/
-int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur)
+/*!
+ * \brief Dispatch a CDR.
+ * \param cdr The \ref cdr_object to dispatch
+ *
+ * This will create a \ref ast_cdr object and publish it to the various backends
+ */
+static void cdr_object_dispatch(struct cdr_object *cdr)
{
- struct ast_var_t *newvariable;
- struct varshead *headp;
- int x;
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct ast_cdr *pub_cdr;
- for (x = 0; cdr_readonly_vars[x]; x++) {
- if (!strcasecmp(name, cdr_readonly_vars[x])) {
- ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name);
- return -1;
+ CDR_DEBUG(mod_cfg, "%p - Dispatching CDR for Party A %s, Party B %s\n", cdr,
+ cdr->party_a.snapshot->name,
+ cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "<none>");
+ pub_cdr = cdr_object_create_public_records(cdr);
+ cdr_detach(pub_cdr);
+}
+
+/*!
+ * \brief Set the disposition on a \ref cdr_object based on a hangupcause code
+ * \param cdr The \ref cdr_object
+ * \param hangupcause The Asterisk hangup cause code
+ */
+static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause)
+{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ /* Change the disposition based on the hang up cause */
+ switch (hangupcause) {
+ case AST_CAUSE_BUSY:
+ cdr->disposition = AST_CDR_BUSY;
+ break;
+ case AST_CAUSE_CONGESTION:
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+ cdr->disposition = AST_CDR_FAILED;
+ } else {
+ cdr->disposition = AST_CDR_CONGESTION;
}
+ break;
+ case AST_CAUSE_NO_ROUTE_DESTINATION:
+ case AST_CAUSE_UNREGISTERED:
+ cdr->disposition = AST_CDR_FAILED;
+ break;
+ case AST_CAUSE_NORMAL_CLEARING:
+ case AST_CAUSE_NO_ANSWER:
+ cdr->disposition = AST_CDR_NOANSWER;
+ break;
+ default:
+ break;
}
+}
- if (!cdr) {
- ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n");
- return -1;
+/*!
+ * \brief Finalize a CDR.
+ *
+ * This function is safe to call multiple times. Note that you can call this
+ * explicitly before going to the finalized state if there's a chance the CDR
+ * will be re-activated, in which case the \ref cdr_object's end time should be
+ * cleared. This function is implicitly called when a CDR transitions to the
+ * finalized state and right before it is dispatched
+ *
+ * \param cdr_object The CDR to finalize
+ */
+static void cdr_object_finalize(struct cdr_object *cdr)
+{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (!ast_tvzero(cdr->end)) {
+ return;
}
+ cdr->end = ast_tvnow();
- for (; cdr; cdr = recur ? cdr->next : NULL) {
- if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- continue;
- headp = &cdr->varshead;
- AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
- if (!strcasecmp(ast_var_name(newvariable), name)) {
- /* there is already such a variable, delete it */
- AST_LIST_REMOVE_CURRENT(entries);
- ast_var_delete(newvariable);
- break;
- }
+ if (cdr->disposition == AST_CDR_NULL) {
+ if (!ast_tvzero(cdr->answer)) {
+ cdr->disposition = AST_CDR_ANSWERED;
+ } else if (cdr->party_a.snapshot->hangupcause) {
+ cdr_object_set_disposition(cdr, cdr->party_a.snapshot->hangupcause);
+ } else if (cdr->party_b.snapshot && cdr->party_b.snapshot->hangupcause) {
+ cdr_object_set_disposition(cdr, cdr->party_b.snapshot->hangupcause);
+ } else {
+ cdr->disposition = AST_CDR_FAILED;
}
- AST_LIST_TRAVERSE_SAFE_END;
+ }
+
+ ast_debug(1, "Finalized CDR for %s - start %ld.%ld answer %ld.%ld end %ld.%ld dispo %s\n",
+ cdr->party_a.snapshot->name,
+ cdr->start.tv_sec,
+ cdr->start.tv_usec,
+ cdr->answer.tv_sec,
+ cdr->answer.tv_usec,
+ cdr->end.tv_sec,
+ cdr->end.tv_usec,
+ ast_cdr_disp2str(cdr->disposition));
+}
- if (value) {
- newvariable = ast_var_assign(name, value);
- AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+/*!
+ * \brief Check to see if a CDR needs to move to the finalized state because
+ * its Party A hungup.
+ */
+static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
+{
+ if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)
+ && cdr->fn_table != &finalized_state_fn_table) {
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
+ }
+}
+
+/*!
+ * \brief Check to see if a CDR needs to be answered based on its Party A.
+ * Note that this is safe to call as much as you want - we won't answer twice
+ */
+static void cdr_object_check_party_a_answer(struct cdr_object *cdr) {
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (cdr->party_a.snapshot->state == AST_STATE_UP && ast_tvzero(cdr->answer)) {
+ cdr->answer = ast_tvnow();
+ CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%ld\n", cdr,
+ cdr->answer.tv_sec,
+ cdr->answer.tv_usec);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Set a variable on a CDR object
+ *
+ * \param headp The header pointer to the variable to set
+ * \param name The name of the variable
+ * \param value The value of the variable
+ *
+ * CDRs that are in a hungup state cannot have their variables set.
+ */
+static void set_variable(struct varshead *headp, const char *name, const char *value)
+{
+ struct ast_var_t *newvariable;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
+ if (!strcasecmp(ast_var_name(newvariable), name)) {
+ AST_LIST_REMOVE_CURRENT(entries);
+ ast_var_delete(newvariable);
+ break;
}
}
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (value) {
+ newvariable = ast_var_assign(name, value);
+ AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+ }
+}
+
+/* \brief Set Caller ID information on a CDR */
+static void cdr_object_update_cid(struct cdr_object_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
+{
+ if (!old_snapshot->snapshot) {
+ set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid);
+ set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr);
+ set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
+ return;
+ }
+ if (!strcmp(old_snapshot->snapshot->caller_dnid, new_snapshot->caller_dnid)) {
+ set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid);
+ }
+ if (!strcmp(old_snapshot->snapshot->caller_subaddr, new_snapshot->caller_subaddr)) {
+ set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr);
+ }
+ if (!strcmp(old_snapshot->snapshot->dialed_subaddr, new_snapshot->dialed_subaddr)) {
+ set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
+ }
+}
+
+/*!
+ * \brief Swap an old \ref cdr_object_snapshot's \ref ast_channel_snapshot for
+ * a new \ref ast_channel_snapshot
+ * \param old_snapshot The old \ref cdr_object_snapshot
+ * \param new_snapshot The new \ref ast_channel_snapshot for old_snapshot
+ */
+static void cdr_object_swap_snapshot(struct cdr_object_snapshot *old_snapshot,
+ struct ast_channel_snapshot *new_snapshot)
+{
+ cdr_object_update_cid(old_snapshot, new_snapshot);
+ if (old_snapshot->snapshot) {
+ ao2_t_ref(old_snapshot->snapshot, -1, "Drop ref for swap");
+ }
+ ao2_t_ref(new_snapshot, +1, "Bump ref for swap");
+ old_snapshot->snapshot = new_snapshot;
+}
+
+/* BASE METHOD IMPLEMENTATIONS */
+
+static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ ast_assert(strcmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
+ cdr_object_swap_snapshot(&cdr->party_a, snapshot);
+
+ /* When Party A is originated to an application and the application exits, the stack
+ * will attempt to clear the application and restore the dummy originate application
+ * of "AppDialX". Prevent that, and any other application changes we might not want
+ * here.
+ */
+ if (!ast_strlen_zero(snapshot->appl) && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) {
+ ast_string_field_set(cdr, appl, snapshot->appl);
+ ast_string_field_set(cdr, data, snapshot->data);
+ }
+
+ ast_string_field_set(cdr, linkedid, snapshot->linkedid);
+ cdr_object_check_party_a_answer(cdr);
+ cdr_object_check_party_a_hangup(cdr);
return 0;
}
-int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr)
+static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- struct ast_var_t *variables, *newvariable = NULL;
- struct varshead *headpa, *headpb;
- const char *var, *val;
- int x = 0;
+ /* In general, most things shouldn't get a bridge leave */
+ ast_assert(0);
+ return 1;
+}
- if (!to_cdr || !from_cdr) /* don't die if one of the pointers is null */
- return 0;
+static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
+{
+ /* In general, most things shouldn't get a dial end. */
+ ast_assert(0);
+ return 0;
+}
- headpa = &from_cdr->varshead;
- headpb = &to_cdr->varshead;
+/* SINGLE STATE */
- AST_LIST_TRAVERSE(headpa,variables,entries) {
- if (variables &&
- (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
- !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
- newvariable = ast_var_assign(var, val);
- AST_LIST_INSERT_HEAD(headpb, newvariable, entries);
- x++;
+static void single_state_init_function(struct cdr_object *cdr) {
+ cdr->start = ast_tvnow();
+ cdr_object_check_party_a_answer(cdr);
+}
+
+static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+ /* This should never happen! */
+ ast_assert(cdr->party_b.snapshot == NULL);
+ ast_assert(0);
+ return;
+}
+
+static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (caller && !strcmp(cdr->party_a.snapshot->name, caller->name)) {
+ cdr_object_swap_snapshot(&cdr->party_a, caller);
+ CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
+ cdr->party_a.snapshot->name);
+ cdr_object_swap_snapshot(&cdr->party_b, peer);
+ CDR_DEBUG(mod_cfg, "%p - Updated Party B %s snapshot\n", cdr,
+ cdr->party_b.snapshot->name);
+ } else if (!strcmp(cdr->party_a.snapshot->name, peer->name)) {
+ /* We're the entity being dialed, i.e., outbound origination */
+ cdr_object_swap_snapshot(&cdr->party_a, peer);
+ CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
+ cdr->party_a.snapshot->name);
+ }
+
+ cdr_object_transition_state(cdr, &dial_state_fn_table);
+ return 0;
+}
+
+/*!
+ * \brief Handle a comparison between our \ref cdr_object and a \ref cdr_object
+ * already in the bridge while in the Single state. The goal of this is to find
+ * a Party B for our CDR.
+ *
+ * \param cdr Our \ref cdr_object in the Single state
+ * \param cand_cdr The \ref cdr_object already in the Bridge state
+ *
+ * \retval 0 The cand_cdr had a Party A or Party B that we could use as our
+ * Party B
+ * \retval 1 No party in the cand_cdr could be used as our Party B
+ */
+static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
+ struct cdr_object *cand_cdr)
+{
+ struct cdr_object_snapshot *party_a;
+
+ /* Try the candidate CDR's Party A first */
+ party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
+ if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
+ if (!cand_cdr->party_b.snapshot) {
+ /* We just stole them - finalize their CDR. Note that this won't
+ * transition their state, it just sets the end time and the
+ * disposition - if we need to re-activate them later, we can.
+ */
+ cdr_object_finalize(cand_cdr);
}
+ return 0;
}
- return x;
+ /* Try their Party B */
+ if (!cand_cdr->party_b.snapshot) {
+ return 1;
+ }
+ party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
+ if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
+ return 0;
+ }
+
+ return 1;
}
-int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur)
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- struct ast_var_t *variables;
- const char *var;
- char *tmp;
- char workspace[256];
- int total = 0, x = 0, i;
+ struct ao2_iterator *it_cdrs;
+ struct cdr_object *cand_cdr_master;
+ char *bridge_id = ast_strdupa(bridge->uniqueid);
+ int success = 1;
- ast_str_reset(*buf);
+ ast_string_field_set(cdr, bridge, bridge->uniqueid);
- for (; cdr; cdr = recur ? cdr->next : NULL) {
- if (++x > 1)
- ast_str_append(buf, 0, "\n");
+ /* Get parties in the bridge */
+ it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+ cdr_object_bridge_cmp_fn, bridge_id);
+ if (!it_cdrs) {
+ /* No one in the bridge yet! */
+ cdr_object_transition_state(cdr, &bridge_state_fn_table);
+ return 0;
+ }
- AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
- if (!(var = ast_var_name(variables))) {
+ while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ struct cdr_object *cand_cdr;
+
+ ao2_lock(cand_cdr_master);
+ for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
+ /* Skip any records that are not in a bridge or in this bridge.
+ * I'm not sure how that would happen, but it pays to be careful. */
+ if (cand_cdr->fn_table != &bridge_state_fn_table ||
+ strcmp(cdr->bridge, cand_cdr->bridge)) {
continue;
}
- if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variables), ""), sep) < 0) {
- ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
- break;
+ if (single_state_bridge_enter_comparison(cdr, cand_cdr)) {
+ continue;
}
+ /* We successfully got a party B - break out */
+ success = 0;
+ break;
+ }
+ ao2_unlock(cand_cdr_master);
+ ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
+ }
+ ao2_iterator_destroy(it_cdrs);
- total++;
+ /* We always transition state, even if we didn't get a peer */
+ cdr_object_transition_state(cdr, &bridge_state_fn_table);
+
+ /* Success implies that we have a Party B */
+ return success;
+}
+
+/* DIAL STATE */
+
+static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+ ast_assert(snapshot != NULL);
+
+ if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) {
+ return;
+ }
+ cdr_object_swap_snapshot(&cdr->party_b, snapshot);
+
+ /* If party B hangs up, finalize this CDR */
+ if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
+ }
+}
+
+static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+ /* Don't process a begin dial here. A party A already in the dial state will
+ * who receives a dial begin for something else will be handled by the
+ * message router callback and will add a new CDR for the party A */
+ return 1;
+}
+
+/*! \internal \brief Convert a dial status to a CDR disposition */
+static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_status)
+{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (!strcmp(dial_status, "ANSWER")) {
+ return AST_CDR_ANSWERED;
+ } else if (!strcmp(dial_status, "BUSY")) {
+ return AST_CDR_BUSY;
+ } else if (!strcmp(dial_status, "CANCEL") || !strcmp(dial_status, "NOANSWER")) {
+ return AST_CDR_NOANSWER;
+ } else if (!strcmp(dial_status, "CONGESTION")) {
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+ return AST_CDR_FAILED;
+ } else {
+ return AST_CDR_CONGESTION;
}
+ } else if (!strcmp(dial_status, "FAILED")) {
+ return AST_CDR_FAILED;
+ }
+ return AST_CDR_FAILED;
+}
- for (i = 0; cdr_readonly_vars[i]; i++) {
- workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
- ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
- if (!tmp)
+static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
+{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct ast_channel_snapshot *party_a;
+
+ if (caller) {
+ party_a = caller;
+ } else {
+ party_a = peer;
+ }
+ ast_assert(!strcmp(cdr->party_a.snapshot->name, party_a->name));
+ cdr_object_swap_snapshot(&cdr->party_a, party_a);
+
+ if (cdr->party_b.snapshot) {
+ if (strcmp(cdr->party_b.snapshot->name, peer->name)) {
+ /* Not the status for this CDR - defer back to the message router */
+ return 1;
+ }
+ cdr_object_swap_snapshot(&cdr->party_b, peer);
+ }
+
+ /* Set the disposition based on the dial string. */
+ cdr->disposition = dial_status_to_disposition(dial_status);
+ if (cdr->disposition == AST_CDR_ANSWERED) {
+ /* Switch to dial pending to wait and see what the caller does */
+ cdr_object_transition_state(cdr, &dialed_pending_state_fn_table);
+ } else {
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
+ }
+
+ return 0;
+}
+
+static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+ struct ao2_iterator *it_cdrs;
+ char *bridge_id = ast_strdupa(bridge->uniqueid);
+ struct cdr_object *cand_cdr_master;
+ int success = 1;
+
+ ast_string_field_set(cdr, bridge, bridge->uniqueid);
+
+ /* Get parties in the bridge */
+ it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+ cdr_object_bridge_cmp_fn, bridge_id);
+ if (!it_cdrs) {
+ /* No one in the bridge yet! */
+ cdr_object_transition_state(cdr, &bridge_state_fn_table);
+ return 0;
+ }
+
+ while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ struct cdr_object *cand_cdr;
+
+ ao2_lock(cand_cdr_master);
+ for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
+ /* Skip any records that are not in a bridge or in this bridge.
+ * I'm not sure how that would happen, but it pays to be careful. */
+ if (cand_cdr->fn_table != &bridge_state_fn_table ||
+ strcmp(cdr->bridge, cand_cdr->bridge)) {
continue;
+ }
- if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) {
- ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
- break;
- } else
- total++;
+ /* Skip any records that aren't our Party B */
+ if (strcmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) {
+ continue;
+ }
+
+ cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
+ /* If they have a Party B, they joined up with someone else as their
+ * Party A. Don't finalize them as they're active. Otherwise, we
+ * have stolen them so they need to be finalized.
+ */
+ if (!cand_cdr->party_b.snapshot) {
+ cdr_object_finalize(cand_cdr);
+ }
+ success = 0;
+ break;
}
+ ao2_unlock(cand_cdr_master);
+ ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
}
+ ao2_iterator_destroy(it_cdrs);
- return total;
+ /* We always transition state, even if we didn't get a peer */
+ cdr_object_transition_state(cdr, &bridge_state_fn_table);
+
+ /* Success implies that we have a Party B */
+ return success;
}
+/* DIALED PENDING STATE */
-void ast_cdr_free_vars(struct ast_cdr *cdr, int recur)
+static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
-
- /* clear variables */
- for (; cdr; cdr = recur ? cdr->next : NULL) {
- struct ast_var_t *vardata;
- struct varshead *headp = &cdr->varshead;
- while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries)))
- ast_var_delete(vardata);
+ /* If we get a CEP change, we're executing dialplan. If we have a Party B
+ * that means we need a new CDR; otherwise, switch us over to single.
+ */
+ if (strcmp(snapshot->context, cdr->party_a.snapshot->context)
+ || strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
+ || snapshot->priority != cdr->party_a.snapshot->priority
+ || strcmp(snapshot->appl, cdr->party_a.snapshot->appl)) {
+ if (cdr->party_b.snapshot) {
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
+ cdr->fn_table->process_party_a(cdr, snapshot);
+ return 1;
+ } else {
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+ cdr->fn_table->process_party_a(cdr, snapshot);
+ return 0;
+ }
}
+ base_process_party_a(cdr, snapshot);
+ return 0;
}
-/*! \brief print a warning if cdr already posted */
-static void check_post(struct ast_cdr *cdr)
+static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- if (!cdr)
- return;
- if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
- ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>"));
+ cdr_object_transition_state(cdr, &dial_state_fn_table);
+ return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
}
-void ast_cdr_free(struct ast_cdr *cdr)
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
{
+ struct cdr_object *new_cdr;
- while (cdr) {
- struct ast_cdr *next = cdr->next;
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
+ new_cdr = cdr_object_create_and_append(cdr);
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+ return new_cdr->fn_table->process_dial_begin(cdr, caller, peer);
+}
- ast_cdr_free_vars(cdr, 0);
- ast_free(cdr);
- cdr = next;
+/* BRIDGE STATE */
+
+static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+ if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) {
+ return;
+ }
+ cdr_object_swap_snapshot(&cdr->party_b, snapshot);
+
+ /* If party B hangs up, finalize this CDR */
+ if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
}
}
-/*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
-void ast_cdr_discard(struct ast_cdr *cdr)
+static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- while (cdr) {
- struct ast_cdr *next = cdr->next;
+ if (strcmp(cdr->bridge, bridge->uniqueid)) {
+ return 1;
+ }
+ if (strcmp(cdr->party_a.snapshot->name, channel->name)
+ && cdr->party_b.snapshot
+ && strcmp(cdr->party_b.snapshot->name, channel->name)) {
+ return 1;
+ }
+ cdr_object_transition_state(cdr, &finalized_state_fn_table);
- ast_cdr_free_vars(cdr, 0);
- ast_free(cdr);
- cdr = next;
+ return 0;
+}
+
+/* PENDING STATE */
+
+static void pending_state_init_function(struct cdr_object *cdr)
+{
+ ast_cdr_set_property(cdr->name, AST_CDR_FLAG_DISABLE);
+}
+
+static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+ if (ast_test_flag(&snapshot->flags, AST_FLAG_ZOMBIE)) {
+ return 0;
+ }
+
+ /* Ignore if we don't get a CEP change */
+ if (!strcmp(snapshot->context, cdr->party_a.snapshot->context)
+ && !strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
+ && snapshot->priority == cdr->party_a.snapshot->priority) {
+ return 0;
}
+
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+ ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+ cdr->fn_table->process_party_a(cdr, snapshot);
+ return 0;
}
-struct ast_cdr *ast_cdr_alloc(void)
+static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
{
- struct ast_cdr *x;
- x = ast_calloc(1, sizeof(*x));
- if (!x)
- ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n");
- return x;
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+ ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+ return cdr->fn_table->process_dial_begin(cdr, caller, peer);
}
-static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from)
+static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- struct ast_var_t *variablesfrom,*variablesto;
- struct varshead *headpfrom = &to->varshead;
- struct varshead *headpto = &from->varshead;
- AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) {
- /* for every var in from, stick it in to */
- const char *fromvarname, *fromvarval;
- const char *tovarname = NULL, *tovarval = NULL;
- fromvarname = ast_var_name(variablesfrom);
- fromvarval = ast_var_value(variablesfrom);
- tovarname = 0;
+ cdr_object_transition_state(cdr, &single_state_fn_table);
+ ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+ return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
+}
- /* now, quick see if that var is in the 'to' cdr already */
- AST_LIST_TRAVERSE(headpto, variablesto, entries) {
+/* FINALIZED STATE */
- /* now, quick see if that var is in the 'to' cdr already */
- if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) {
- tovarname = ast_var_name(variablesto);
- tovarval = ast_var_value(variablesto);
- break;
- }
- }
- if (tovarname && strcasecmp(fromvarval,tovarval) != 0) { /* this message here to see how irritating the userbase finds it */
- ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval);
- continue;
- } else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */
- continue;
+static void finalized_state_init_function(struct cdr_object *cdr)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
- /* rip this var out of the from cdr, and stick it in the to cdr */
- AST_LIST_MOVE_CURRENT(headpto, entries);
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+ return;
}
- AST_LIST_TRAVERSE_SAFE_END;
+
+ cdr_object_finalize(cdr);
}
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from)
+static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
- struct ast_cdr *zcdr;
- struct ast_cdr *lto = NULL;
- struct ast_cdr *lfrom = NULL;
- int discard_from = 0;
+ if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) {
+ cdr_object_finalize(cdr);
+ }
- if (!to || !from)
- return;
+ /* Indicate that, if possible, we should get a new CDR */
+ return 1;
+}
- /* don't merge into locked CDR's -- it's bad business */
- if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
- zcdr = to; /* safety valve? */
- while (to->next) {
- lto = to;
- to = to->next;
- }
+/* TOPIC ROUTER CALLBACKS */
+
+/*!
+ * \brief Handler for Stasis-Core dial messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message
+ */
+static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr_caller, NULL, ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr_peer, NULL, ao2_cleanup);
+ struct cdr_object *cdr;
+ struct ast_multi_channel_blob *payload = stasis_message_data(message);
+ struct ast_channel_snapshot *caller;
+ struct ast_channel_snapshot *peer;
+ struct cdr_object_snapshot *party_a;
+ struct cdr_object_snapshot *party_b;
+ struct cdr_object *it_cdr;
+ struct ast_json *dial_status_blob;
+ const char *dial_status = NULL;
+ int res = 1;
+
+ CDR_DEBUG(mod_cfg, "Dial message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+ ast_assert(payload != NULL);
+
+ caller = ast_multi_channel_blob_get_channel(payload, "caller");
+ peer = ast_multi_channel_blob_get_channel(payload, "peer");
+ if (!peer && !caller) {
+ return;
+ }
+ dial_status_blob = ast_json_object_get(ast_multi_channel_blob_get_json(payload), "dialstatus");
+ if (dial_status_blob) {
+ dial_status = ast_json_string_get(dial_status_blob);
+ }
- if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
- ast_log(LOG_WARNING, "Merging into locked CDR... no choice.\n");
- to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */
- lto = NULL;
+ /* Figure out who is running this show */
+ if (caller) {
+ cdr_caller = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
+ }
+ if (peer) {
+ cdr_peer = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
+ }
+ if (cdr_caller && cdr_peer) {
+ party_a = cdr_object_pick_party_a(&cdr_caller->party_a, &cdr_peer->party_a);
+ if (!strcmp(party_a->snapshot->name, cdr_caller->party_a.snapshot->name)) {
+ cdr = cdr_caller;
+ party_b = &cdr_peer->party_a;
+ } else {
+ cdr = cdr_peer;
+ party_b = &cdr_caller->party_a;
}
+ } else if (cdr_caller) {
+ cdr = cdr_caller;
+ party_a = &cdr_caller->party_a;
+ party_b = NULL;
+ } else if (cdr_peer) {
+ cdr = cdr_peer;
+ party_a = NULL;
+ party_b = &cdr_peer->party_a;
+ } else {
+ return;
}
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) {
- struct ast_cdr *llfrom = NULL;
- discard_from = 1;
- if (lto) {
- /* insert the from stuff after lto */
- lto->next = from;
- lfrom = from;
- while (lfrom && lfrom->next) {
- if (!lfrom->next->next)
- llfrom = lfrom;
- lfrom = lfrom->next;
- }
- /* rip off the last entry and put a copy of the to at the end */
- if (llfrom) {
- llfrom->next = to;
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (ast_strlen_zero(dial_status)) {
+ if (!it_cdr->fn_table->process_dial_begin) {
+ continue;
}
- from = lfrom;
+ CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n",
+ cdr,
+ party_a ? party_a->snapshot->name : "(none)",
+ party_b ? party_b->snapshot->name : "(none)");
+ res &= it_cdr->fn_table->process_dial_begin(it_cdr,
+ party_a ? party_a->snapshot : NULL,
+ party_b ? party_b->snapshot : NULL);
} else {
- /* save copy of the current *to cdr */
- struct ast_cdr tcdr;
- memcpy(&tcdr, to, sizeof(tcdr));
- /* copy in the locked from cdr */
- memcpy(to, from, sizeof(*to));
- lfrom = from;
- while (lfrom && lfrom->next) {
- if (!lfrom->next->next)
- llfrom = lfrom;
- lfrom = lfrom->next;
- }
- from->next = NULL;
- /* rip off the last entry and put a copy of the to at the end */
- if (llfrom == from) {
- to = to->next = ast_cdr_dup(&tcdr);
- } else if (llfrom) {
- to = llfrom->next = ast_cdr_dup(&tcdr);
+ if (!it_cdr->fn_table->process_dial_end) {
+ continue;
}
- from = lfrom;
+ CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n",
+ cdr,
+ party_a ? party_a->snapshot->name : "(none)",
+ party_b ? party_b->snapshot->name : "(none)");
+ it_cdr->fn_table->process_dial_end(it_cdr,
+ party_a ? party_a->snapshot : NULL,
+ party_b ? party_b->snapshot : NULL,
+ dial_status);
}
}
- if (!ast_tvzero(from->start)) {
- if (!ast_tvzero(to->start)) {
- if (ast_tvcmp(to->start, from->start) > 0 ) {
- to->start = from->start; /* use the earliest time */
- from->start = ast_tv(0,0); /* we actively "steal" these values */
- }
- /* else nothing to do */
- } else {
- to->start = from->start;
- from->start = ast_tv(0,0); /* we actively "steal" these values */
+ /* If no CDR handled a dial begin message, make a new one */
+ if (res && ast_strlen_zero(dial_status)) {
+ struct cdr_object *new_cdr;
+
+ new_cdr = cdr_object_create_and_append(cdr);
+ if (!new_cdr) {
+ return;
}
+ new_cdr->fn_table->process_dial_begin(new_cdr,
+ party_a ? party_a->snapshot : NULL,
+ party_b ? party_b->snapshot : NULL);
}
- if (!ast_tvzero(from->answer)) {
- if (!ast_tvzero(to->answer)) {
- if (ast_tvcmp(to->answer, from->answer) > 0 ) {
- to->answer = from->answer; /* use the earliest time */
- from->answer = ast_tv(0,0); /* we actively "steal" these values */
- }
- /* we got the earliest answer time, so we'll settle for that? */
- } else {
- to->answer = from->answer;
- from->answer = ast_tv(0,0); /* we actively "steal" these values */
+ ao2_unlock(cdr);
+}
+
+static int cdr_object_finalize_party_b(void *obj, void *arg, int flags)
+{
+ struct cdr_object *cdr = obj;
+ struct ast_channel_snapshot *party_b = arg;
+ struct cdr_object *it_cdr;
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) {
+ /* Don't transition to the finalized state - let the Party A do
+ * that when its ready
+ */
+ cdr_object_finalize(it_cdr);
}
}
- if (!ast_tvzero(from->end)) {
- if (!ast_tvzero(to->end)) {
- if (ast_tvcmp(to->end, from->end) < 0 ) {
- to->end = from->end; /* use the latest time */
- from->end = ast_tv(0,0); /* we actively "steal" these values */
- to->duration = to->end.tv_sec - to->start.tv_sec; /* don't forget to update the duration, billsec, when we set end */
- to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
- }
- /* else, nothing to do */
- } else {
- to->end = from->end;
- from->end = ast_tv(0,0); /* we actively "steal" these values */
- to->duration = to->end.tv_sec - to->start.tv_sec;
- to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
+ return 0;
+}
+
+static int cdr_object_update_party_b(void *obj, void *arg, int flags)
+{
+ struct cdr_object *cdr = obj;
+ struct ast_channel_snapshot *party_b = arg;
+ struct cdr_object *it_cdr;
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (!it_cdr->fn_table->process_party_b) {
+ continue;
+ }
+ if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) {
+ it_cdr->fn_table->process_party_b(it_cdr, party_b);
}
}
- if (to->disposition < from->disposition) {
- to->disposition = from->disposition;
- from->disposition = AST_CDR_NOANSWER;
+ return 0;
+}
+
+/*! \internal \brief Filter channel snapshots by technology */
+static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+{
+ if (!strncmp(snapshot->name, "CBAnn", 5) ||
+ !strncmp(snapshot->name, "CBRec", 5)) {
+ return 1;
}
- if (ast_strlen_zero(to->lastapp) && !ast_strlen_zero(from->lastapp)) {
- ast_copy_string(to->lastapp, from->lastapp, sizeof(to->lastapp));
- from->lastapp[0] = 0; /* theft */
+ return 0;
+}
+
+/*! \internal \brief Filter a channel cache update */
+static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
+ struct ast_channel_snapshot *new_snapshot)
+{
+ int ret = 0;
+
+ /* Drop cache updates from certain channel technologies */
+ if (old_snapshot) {
+ ret |= filter_channel_snapshot(old_snapshot);
}
- if (ast_strlen_zero(to->lastdata) && !ast_strlen_zero(from->lastdata)) {
- ast_copy_string(to->lastdata, from->lastdata, sizeof(to->lastdata));
- from->lastdata[0] = 0; /* theft */
+ if (new_snapshot) {
+ ret |= filter_channel_snapshot(new_snapshot);
}
- if (ast_strlen_zero(to->dcontext) && !ast_strlen_zero(from->dcontext)) {
- ast_copy_string(to->dcontext, from->dcontext, sizeof(to->dcontext));
- from->dcontext[0] = 0; /* theft */
+
+ return ret;
+}
+
+/*! \brief Determine if we need to add a new CDR based on snapshots */
+static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
+ struct ast_channel_snapshot *new_snapshot)
+{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (!new_snapshot) {
+ return 0;
}
- if (ast_strlen_zero(to->dstchannel) && !ast_strlen_zero(from->dstchannel)) {
- ast_copy_string(to->dstchannel, from->dstchannel, sizeof(to->dstchannel));
- from->dstchannel[0] = 0; /* theft */
+
+ if (ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE)) {
+ return 0;
}
- if (!ast_strlen_zero(from->channel) && (ast_strlen_zero(to->channel) || !strncasecmp(from->channel, "Agent/", 6))) {
- ast_copy_string(to->channel, from->channel, sizeof(to->channel));
- from->channel[0] = 0; /* theft */
+
+ /* Auto-fall through will increment the priority but have no application */
+ if (ast_strlen_zero(new_snapshot->appl)) {
+ return 0;
}
- if (ast_strlen_zero(to->src) && !ast_strlen_zero(from->src)) {
- ast_copy_string(to->src, from->src, sizeof(to->src));
- from->src[0] = 0; /* theft */
+
+ if (old_snapshot && !strcmp(old_snapshot->context, new_snapshot->context)
+ && !strcmp(old_snapshot->exten, new_snapshot->exten)
+ && old_snapshot->priority == new_snapshot->priority
+ && !(strcmp(old_snapshot->appl, new_snapshot->appl))) {
+ return 0;
}
- if (ast_strlen_zero(to->clid) && !ast_strlen_zero(from->clid)) {
- ast_copy_string(to->clid, from->clid, sizeof(to->clid));
- from->clid[0] = 0; /* theft */
+
+ return 1;
+}
+
+/*!
+ * \brief Handler for Stasis-Core channel cache update messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message
+ */
+static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+ RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct stasis_cache_update *update = stasis_message_data(message);
+ struct ast_channel_snapshot *old_snapshot;
+ struct ast_channel_snapshot *new_snapshot;
+ const char *name;
+ struct cdr_object *it_cdr;
+
+ ast_assert(update != NULL);
+ if (ast_channel_snapshot_type() != update->type) {
+ return;
}
- if (ast_strlen_zero(to->dst) && !ast_strlen_zero(from->dst)) {
- ast_copy_string(to->dst, from->dst, sizeof(to->dst));
- from->dst[0] = 0; /* theft */
+
+ old_snapshot = stasis_message_data(update->old_snapshot);
+ new_snapshot = stasis_message_data(update->new_snapshot);
+ name = new_snapshot ? new_snapshot->name : old_snapshot->name;
+
+ if (filter_channel_cache_message(old_snapshot, new_snapshot)) {
+ return;
}
- if (!to->amaflags)
- to->amaflags = AST_CDR_DOCUMENTATION;
- if (!from->amaflags)
- from->amaflags = AST_CDR_DOCUMENTATION; /* make sure both amaflags are set to something (DOC is default) */
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (to->amaflags == AST_CDR_DOCUMENTATION && from->amaflags != AST_CDR_DOCUMENTATION)) {
- to->amaflags = from->amaflags;
+
+ CDR_DEBUG(mod_cfg, "Channel Update message for %s: %u.%08u\n",
+ name,
+ (unsigned int)stasis_message_timestamp(message)->tv_sec,
+ (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+ if (new_snapshot && !old_snapshot) {
+ cdr = cdr_object_alloc(new_snapshot);
+ if (!cdr) {
+ return;
+ }
+ ao2_link(active_cdrs_by_channel, cdr);
}
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) {
- ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode));
+
+ /* Handle Party A */
+ if (!cdr) {
+ cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
}
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) {
- ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount));
+ if (!cdr) {
+ ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
+ } else {
+ ao2_lock(cdr);
+ if (new_snapshot) {
+ int all_reject = 1;
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (!it_cdr->fn_table->process_party_a) {
+ continue;
+ }
+ CDR_DEBUG(mod_cfg, "%p - Processing new channel snapshot %s\n", it_cdr, new_snapshot->name);
+ all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot);
+ }
+ if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) {
+ /* We're not hung up and we have a new snapshot - we need a new CDR */
+ struct cdr_object *new_cdr;
+ new_cdr = cdr_object_create_and_append(cdr);
+ new_cdr->fn_table->process_party_a(new_cdr, new_snapshot);
+ }
+ } else {
+ CDR_DEBUG(mod_cfg, "%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ cdr_object_finalize(it_cdr);
+ }
+ cdr_object_dispatch(cdr);
+ ao2_unlink(active_cdrs_by_channel, cdr);
+ }
+ ao2_unlock(cdr);
}
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) {
- ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield));
+
+ /* Handle Party B */
+ if (new_snapshot) {
+ ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_update_party_b,
+ new_snapshot);
+ } else {
+ ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_finalize_party_b,
+ old_snapshot);
}
- /* flags, varsead, ? */
- cdr_merge_vars(from, to);
- if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS))
- ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS);
- if (ast_test_flag(from, AST_CDR_FLAG_POSTED))
- ast_set_flag(to, AST_CDR_FLAG_POSTED);
- if (ast_test_flag(from, AST_CDR_FLAG_LOCKED))
- ast_set_flag(to, AST_CDR_FLAG_LOCKED);
- if (ast_test_flag(from, AST_CDR_FLAG_CHILD))
- ast_set_flag(to, AST_CDR_FLAG_CHILD);
- if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED))
- ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED);
+}
- /* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
- while (from->next) {
- /* just rip 'em off the 'from' and insert them on the 'to' */
- zcdr = from->next;
- from->next = zcdr->next;
- zcdr->next = NULL;
- /* zcdr is now ripped from the current list; */
- ast_cdr_append(to, zcdr);
+struct bridge_leave_data {
+ struct ast_bridge_snapshot *bridge;
+ struct ast_channel_snapshot *channel;
+};
+
+/*! \brief Callback used to notify CDRs of a Party B leaving the bridge */
+static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags)
+{
+ struct cdr_object *cdr = obj;
+ struct bridge_leave_data *leave_data = arg;
+ struct cdr_object *it_cdr;
+
+ if (strcmp(cdr->bridge, leave_data->bridge->uniqueid)) {
+ return 0;
}
- if (discard_from)
- ast_cdr_discard(from);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table != &bridge_state_fn_table) {
+ continue;
+ }
+ if (!it_cdr->party_b.snapshot) {
+ continue;
+ }
+ if (strcmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) {
+ continue;
+ }
+ if (!it_cdr->fn_table->process_bridge_leave(it_cdr, leave_data->bridge, leave_data->channel)) {
+ /* Update the end times for this CDR. We don't want to actually
+ * finalize it, as the Party A will eventually need to leave, which
+ * will switch the records to pending bridged.
+ */
+ cdr_object_finalize(it_cdr);
+ }
+ }
+ return 0;
}
-void ast_cdr_start(struct ast_cdr *cdr)
+/*! \brief Filter bridge messages based on bridge technology */
+static int filter_bridge_messages(struct ast_bridge_snapshot *bridge)
{
- for (; cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- cdr->start = ast_tvnow();
+ /* Ignore holding bridge technology messages. We treat this simply as an application
+ * that a channel enters into.
+ */
+ if (!strcmp(bridge->technology, "holding_bridge")) {
+ return 1;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Handler for when a channel leaves a bridge
+ * \param bridge The \ref ast_bridge_snapshot representing the bridge
+ * \param channel The \ref ast_channel_snapshot representing the channel
+ */
+static void handle_bridge_leave_message(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic, struct stasis_message *message)
+{
+ struct ast_bridge_blob *update = stasis_message_data(message);
+ struct ast_bridge_snapshot *bridge = update->bridge;
+ struct ast_channel_snapshot *channel = update->channel;
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *it_cdr;
+ struct cdr_object *pending_cdr;
+ struct bridge_leave_data leave_data = {
+ .bridge = bridge,
+ .channel = channel,
+ };
+ int left_bridge = 0;
+
+ if (filter_bridge_messages(bridge)) {
+ return;
+ }
+
+ CDR_DEBUG(mod_cfg, "Bridge Leave message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+ if (!cdr) {
+ ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+ return;
+ }
+
+ /* Party A */
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (!it_cdr->fn_table->process_bridge_leave) {
+ continue;
+ }
+ CDR_DEBUG(mod_cfg, "%p - Processing Bridge Leave for %s\n",
+ it_cdr, channel->name);
+ if (!it_cdr->fn_table->process_bridge_leave(it_cdr, bridge, channel)) {
+ ast_string_field_set(it_cdr, bridge, "");
+ left_bridge = 1;
}
}
+ if (!left_bridge) {
+ ao2_unlock(cdr);
+ return;
+ }
+
+ ao2_unlink(active_cdrs_by_bridge, cdr);
+
+ /* Create a new pending record. If the channel decides to do something else,
+ * the pending record will handle it - otherwise, if gets dropped.
+ */
+ pending_cdr = cdr_object_create_and_append(cdr);
+ cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table);
+ ao2_unlock(cdr);
+
+ /* Party B */
+ ao2_callback(active_cdrs_by_bridge, OBJ_NODATA,
+ cdr_object_party_b_left_bridge_cb,
+ &leave_data);
+}
+
+struct bridge_candidate {
+ struct cdr_object *cdr; /*!< The actual CDR this candidate belongs to, either as A or B */
+ struct cdr_object_snapshot candidate; /*!< The candidate for a new pairing */
+};
+
+/*! \internal
+ * \brief Comparison function for \ref bridge_candidate objects
+ */
+static int bridge_candidate_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct bridge_candidate *left = obj;
+ struct bridge_candidate *right = arg;
+ const char *match = (flags & OBJ_KEY) ? arg : right->candidate.snapshot->name;
+ return strcasecmp(left->candidate.snapshot->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
}
-void ast_cdr_answer(struct ast_cdr *cdr)
+/*! \internal
+ * \brief Hash function for \ref bridge_candidate objects
+ */
+static int bridge_candidate_hash_fn(const void *obj, const int flags)
{
+ const struct bridge_candidate *bc = obj;
+ const char *id = (flags & OBJ_KEY) ? obj : bc->candidate.snapshot->name;
+ return ast_str_case_hash(id);
+}
- for (; cdr; cdr = cdr->next) {
- if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
- continue;
- if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- continue;
- check_post(cdr);
- if (cdr->disposition < AST_CDR_ANSWERED)
- cdr->disposition = AST_CDR_ANSWERED;
- if (ast_tvzero(cdr->answer))
- cdr->answer = ast_tvnow();
+/*! \brief \ref bridge_candidate Destructor */
+static void bridge_candidate_dtor(void *obj)
+{
+ struct bridge_candidate *bcand = obj;
+ ao2_cleanup(bcand->cdr);
+ ao2_cleanup(bcand->candidate.snapshot);
+ free_variables(&bcand->candidate.variables);
+}
+
+/*!
+ * \brief \ref bridge_candidate Constructor
+ * \param cdr The \ref cdr_object that is a candidate for being compared to in
+ * a bridge operation
+ * \param candidate The \ref cdr_object_snapshot candidate snapshot in the CDR
+ * that should be used during the operaton
+ */
+static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct cdr_object_snapshot *candidate)
+{
+ struct bridge_candidate *bcand;
+
+ bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor);
+ if (!bcand) {
+ return NULL;
}
+ bcand->cdr = cdr;
+ ao2_ref(bcand->cdr, +1);
+ bcand->candidate.flags = candidate->flags;
+ strcpy(bcand->candidate.userfield, candidate->userfield);
+ bcand->candidate.snapshot = candidate->snapshot;
+ ao2_ref(bcand->candidate.snapshot, +1);
+ copy_variables(&bcand->candidate.variables, &candidate->variables);
+
+ return bcand;
}
-void ast_cdr_busy(struct ast_cdr *cdr)
+/*!
+ * \internal \brief Build and add bridge candidates based on a CDR
+ * \param bridge_id The ID of the bridge we need candidates for
+ * \param candidates The container of \ref bridge_candidate objects
+ * \param cdr The \ref cdr_object that is our candidate
+ * \param party_a Non-zero if we should look at the Party A channel; 0 if Party B
+ */
+static void add_candidate_for_bridge(const char *bridge_id,
+ struct ao2_container *candidates,
+ struct cdr_object *cdr,
+ int party_a)
{
+ struct cdr_object *it_cdr;
+
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ struct cdr_object_snapshot *party_snapshot;
+ RAII_VAR(struct bridge_candidate *, bcand, NULL, ao2_cleanup);
- for (; cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- cdr->disposition = AST_CDR_BUSY;
+ party_snapshot = party_a ? &it_cdr->party_a : &it_cdr->party_b;
+
+ if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge_id, it_cdr->bridge)) {
+ continue;
+ }
+
+ if (!party_snapshot->snapshot) {
+ continue;
+ }
+
+ /* Don't add a party twice */
+ bcand = ao2_find(candidates, party_snapshot->snapshot->name, OBJ_KEY);
+ if (bcand) {
+ continue;
+ }
+
+ bcand = bridge_candidate_alloc(it_cdr, party_snapshot);
+ if (bcand) {
+ ao2_link(candidates, bcand);
}
}
}
-void ast_cdr_failed(struct ast_cdr *cdr)
+/*!
+ * \brief Create new \ref bridge_candidate objects for each party currently
+ * in a bridge
+ * \param bridge The \param ast_bridge_snapshot for the bridge we're processing
+ *
+ * Note that we use two passes here instead of one so that we only create a
+ * candidate for a party B if they are never a party A in the bridge. Otherwise,
+ * we don't care about them.
+ */
+static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snapshot *bridge)
{
- for (; cdr; cdr = cdr->next) {
- check_post(cdr);
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- if (cdr->disposition < AST_CDR_FAILED)
- cdr->disposition = AST_CDR_FAILED;
- }
+ struct ao2_container *candidates = ao2_container_alloc(51, bridge_candidate_hash_fn, bridge_candidate_cmp_fn);
+ char *bridge_id = ast_strdupa(bridge->uniqueid);
+ struct ao2_iterator *it_cdrs;
+ struct cdr_object *cand_cdr_master;
+
+ if (!candidates) {
+ return NULL;
+ }
+
+ /* For each CDR that has a record in the bridge, get their Party A and
+ * make them a candidate. Note that we do this in two passes as opposed to one so
+ * that we give preference CDRs where the channel is Party A */
+ it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+ cdr_object_bridge_cmp_fn, bridge_id);
+ if (!it_cdrs) {
+ /* No one in the bridge yet! */
+ ao2_cleanup(candidates);
+ return NULL;
+ }
+ while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ SCOPED_AO2LOCK(lock, cand_cdr_master);
+ add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 1);
+ }
+ ao2_iterator_destroy(it_cdrs);
+
+ /* For each CDR that has a record in the bridge, get their Party B and
+ * make them a candidate. */
+ it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+ cdr_object_bridge_cmp_fn, bridge_id);
+ if (!it_cdrs) {
+ /* Now it's just an error. */
+ ao2_cleanup(candidates);
+ return NULL;
+ }
+ while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ SCOPED_AO2LOCK(lock, cand_cdr_master);
+ add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 0);
}
+ ao2_iterator_destroy(it_cdrs);
+
+ return candidates;
}
-void ast_cdr_noanswer(struct ast_cdr *cdr)
+/*!
+ * \internal \brief Create a new CDR, append it to an existing CDR, and update its snapshots
+ * \note The new CDR will be automatically transitioned to the bridge state
+ */
+static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
+ const char *bridge_id,
+ struct cdr_object_snapshot *party_b)
{
- while (cdr) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- cdr->disposition = AST_CDR_NOANSWER;
+ struct cdr_object *new_cdr;
+
+ new_cdr = cdr_object_create_and_append(cdr);
+ cdr_object_snapshot_copy(&new_cdr->party_b, party_b);
+ cdr_object_check_party_a_answer(new_cdr);
+ ast_string_field_set(new_cdr, bridge, cdr->bridge);
+ cdr_object_transition_state(new_cdr, &bridge_state_fn_table);
+}
+
+/*!
+ * \brief Process a single \ref bridge_candidate. Note that this is called as
+ * part of an \ref ao2_callback on an \ref ao2_container of \ref bridge_candidate
+ * objects previously created by \ref create_candidates_for_bridge.
+ *
+ * \param obj The \ref bridge_candidate being processed
+ * \param arg The \ref cdr_object that is being compared against the candidates
+ *
+ * The purpose of this function is to create the necessary CDR entries as a
+ * result of \ref cdr_object having entered the same bridge as the CDR
+ * represented by \ref bridge_candidate.
+ */
+static int bridge_candidate_process(void *obj, void *arg, int flags)
+{
+ struct bridge_candidate *bcand = obj;
+ struct cdr_object *cdr = arg;
+ struct cdr_object_snapshot *party_a;
+
+ /* If the candidate is us or someone we've taken on, pass on by */
+ if (!strcmp(cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)
+ || (cdr->party_b.snapshot && !(strcmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name)))) {
+ return 0;
+ }
+
+ party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate);
+ /* We're party A - make a new CDR, append it to us, and set the candidate as
+ * Party B */
+ if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ bridge_candidate_add_to_cdr(cdr, cdr->bridge, &bcand->candidate);
+ return 0;
+ }
+
+ /* We're Party B. Check if the candidate is the CDR's Party A. If so, find out if we
+ * can add ourselves directly as the Party B, or if we need a new CDR. */
+ if (!strcmp(bcand->cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)) {
+ if (bcand->cdr->party_b.snapshot
+ && strcmp(bcand->cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
+ bridge_candidate_add_to_cdr(bcand->cdr, cdr->bridge, &cdr->party_a);
+ } else {
+ cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a);
+ /* It's possible that this joined at one point and was never chosen
+ * as party A. Clear their end time, as it would be set in such a
+ * case.
+ */
+ memset(&bcand->cdr->end, 0, sizeof(bcand->cdr->end));
}
- cdr = cdr->next;
+ } else {
+ /* We are Party B to a candidate CDR's Party B. Since a candidate
+ * CDR will only have a Party B represented here if that channel
+ * was never a Party A in the bridge, we have to go looking for
+ * that channel's primary CDR record.
+ */
+ struct cdr_object *b_party = ao2_find(active_cdrs_by_channel, bcand->candidate.snapshot->name, OBJ_KEY);
+ if (!b_party) {
+ /* Holy cow - no CDR? */
+ b_party = cdr_object_alloc(bcand->candidate.snapshot);
+ cdr_object_snapshot_copy(&b_party->party_a, &bcand->candidate);
+ cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a);
+ cdr_object_check_party_a_answer(b_party);
+ ast_string_field_set(b_party, bridge, cdr->bridge);
+ cdr_object_transition_state(b_party, &bridge_state_fn_table);
+ ao2_link(active_cdrs_by_channel, b_party);
+ } else {
+ bridge_candidate_add_to_cdr(b_party, cdr->bridge, &cdr->party_a);
+ }
+ ao2_link(active_cdrs_by_bridge, b_party);
+ ao2_ref(b_party, -1);
}
+
+ return 0;
}
-void ast_cdr_congestion(struct ast_cdr *cdr)
+/*!
+ * \brief Handle creating bridge pairings for the \ref cdr_object that just
+ * entered a bridge
+ * \param cdr The \ref cdr_object that just entered the bridge
+ * \param bridge The \ref ast_bridge_snapshot representing the bridge it just entered
+ */
+static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge)
{
- char *chan;
+ RAII_VAR(struct ao2_container *, candidates,
+ create_candidates_for_bridge(bridge),
+ ao2_cleanup);
- /* if congestion log is disabled, pass the buck to ast_cdr_failed */
- if (!congestion) {
- ast_cdr_failed(cdr);
+ if (!candidates) {
+ return;
}
- while (cdr && congestion) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
+ ao2_callback(candidates, OBJ_NODATA,
+ bridge_candidate_process,
+ cdr);
- if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) {
- ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
- }
+ return;
+}
+
+/*!
+ * \brief Handler for Stasis-Core bridge enter messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message - hopefully a bridge one!
+ */
+static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic, struct stasis_message *message)
+{
+ struct ast_bridge_blob *update = stasis_message_data(message);
+ struct ast_bridge_snapshot *bridge = update->bridge;
+ struct ast_channel_snapshot *channel = update->channel;
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+ ao2_cleanup);
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+ int res = 1;
+ struct cdr_object *it_cdr;
+ struct cdr_object *handled_cdr = NULL;
+
+ if (filter_bridge_messages(bridge)) {
+ return;
+ }
- if (cdr->disposition < AST_CDR_CONGESTION) {
- cdr->disposition = AST_CDR_CONGESTION;
+ CDR_DEBUG(mod_cfg, "Bridge Enter message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+ if (!cdr) {
+ ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+ return;
+ }
+
+ ao2_lock(cdr);
+
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table->process_party_a) {
+ CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr,
+ channel->name);
+ it_cdr->fn_table->process_party_a(it_cdr, channel);
+ }
+
+ /* Notify all states that they have entered a bridge */
+ if (it_cdr->fn_table->process_bridge_enter) {
+ CDR_DEBUG(mod_cfg, "%p - Processing bridge enter for %s\n", it_cdr,
+ channel->name);
+ res &= it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
+ if (!res && !handled_cdr) {
+ handled_cdr = it_cdr;
}
}
- cdr = cdr->next;
}
+
+ if (res) {
+ /* We didn't win on any - end this CDR. If someone else comes in later
+ * that is Party B to this CDR, it can re-activate this CDR.
+ */
+ cdr_object_finalize(cdr);
+ }
+
+ /* Create the new matchings, but only for either:
+ * * The first CDR in the chain that handled it. This avoids issues with
+ * forked CDRs.
+ * * If no one handled it, the last CDR in the chain. This would occur if
+ * a CDR joined a bridge and it wasn't Party A for anyone. We still need
+ * to make pairings with everyone in the bridge.
+ */
+ if (!handled_cdr) {
+ handled_cdr = cdr->last;
+ }
+ handle_bridge_pairings(handled_cdr, bridge);
+
+ ao2_link(active_cdrs_by_bridge, cdr);
+ ao2_unlock(cdr);
}
-/* everywhere ast_cdr_disposition is called, it will call ast_cdr_failed()
- if ast_cdr_disposition returns a non-zero value */
+struct ast_cdr_config *ast_cdr_get_config(void)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ ao2_ref(mod_cfg->general, +1);
+ return mod_cfg->general;
+}
-int ast_cdr_disposition(struct ast_cdr *cdr, int cause)
+void ast_cdr_set_config(struct ast_cdr_config *config)
{
- int res = 0;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ ao2_cleanup(mod_cfg->general);
+ mod_cfg->general = config;
+ ao2_ref(mod_cfg->general, +1);
+}
- for (; cdr; cdr = cdr->next) {
- switch (cause) { /* handle all the non failure, busy cases, return 0 not to set disposition,
- return -1 to set disposition to FAILED */
- case AST_CAUSE_BUSY:
- ast_cdr_busy(cdr);
- break;
- case AST_CAUSE_NO_ANSWER:
- ast_cdr_noanswer(cdr);
- break;
- case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION:
- ast_cdr_congestion(cdr);
- break;
- case AST_CAUSE_NORMAL:
- break;
- default:
- res = -1;
+int ast_cdr_is_enabled(void)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
+}
+
+int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
+{
+ struct cdr_beitem *i = NULL;
+
+ if (!name)
+ return -1;
+
+ if (!be) {
+ ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
+ return -1;
+ }
+
+ AST_RWLIST_WRLOCK(&be_list);
+ AST_RWLIST_TRAVERSE(&be_list, i, list) {
+ if (!strcasecmp(name, i->name)) {
+ ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
+ AST_RWLIST_UNLOCK(&be_list);
+ return -1;
}
}
- return res;
+
+ if (!(i = ast_calloc(1, sizeof(*i))))
+ return -1;
+
+ i->be = be;
+ ast_copy_string(i->name, name, sizeof(i->name));
+ ast_copy_string(i->desc, desc, sizeof(i->desc));
+
+ AST_RWLIST_INSERT_HEAD(&be_list, i, list);
+ AST_RWLIST_UNLOCK(&be_list);
+
+ return 0;
}
-void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann)
+void ast_cdr_unregister(const char *name)
{
- for (; cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel));
+ struct cdr_beitem *i = NULL;
+
+ AST_RWLIST_WRLOCK(&be_list);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
+ if (!strcasecmp(name, i->name)) {
+ AST_RWLIST_REMOVE_CURRENT(list);
+ break;
}
}
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&be_list);
+
+ if (i) {
+ ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+ ast_free(i);
+ }
}
-void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data)
+struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
{
+ struct ast_cdr *newcdr;
- for (; cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- check_post(cdr);
- ast_copy_string(cdr->lastapp, S_OR(app, ""), sizeof(cdr->lastapp));
- ast_copy_string(cdr->lastdata, S_OR(data, ""), sizeof(cdr->lastdata));
- }
+ if (!cdr) {
+ return NULL;
+ }
+ newcdr = ast_cdr_alloc();
+ if (!newcdr) {
+ return NULL;
}
+
+ memcpy(newcdr, cdr, sizeof(*newcdr));
+ memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
+ copy_variables(&newcdr->varshead, &cdr->varshead);
+ newcdr->next = NULL;
+
+ return newcdr;
}
-void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t)
+static const char *cdr_format_var_internal(struct ast_cdr *cdr, const char *name)
{
+ struct ast_var_t *variables;
+ struct varshead *headp = &cdr->varshead;
- for (; cdr; cdr = cdr->next) {
- if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
- continue;
- if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- continue;
- check_post(cdr);
- cdr->answer = t;
+ if (ast_strlen_zero(name)) {
+ return NULL;
}
+
+ AST_LIST_TRAVERSE(headp, variables, entries) {
+ if (!strcasecmp(name, ast_var_name(variables))) {
+ return ast_var_value(variables);
+ }
+ }
+
+ return '\0';
}
-void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition)
+static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
{
+ if (fmt == NULL) { /* raw mode */
+ snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
+ } else {
+ if (when.tv_sec) {
+ struct ast_tm tm;
- for (; cdr; cdr = cdr->next) {
- if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- continue;
- check_post(cdr);
- cdr->disposition = disposition;
+ ast_localtime(&when, &tm, NULL);
+ ast_strftime(buf, bufsize, fmt, &tm);
+ }
}
}
-/* set cid info for one record */
-static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c)
+void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw)
{
- const char *num;
+ const char *fmt = "%Y-%m-%d %T";
+ const char *varbuf;
if (!cdr) {
return;
}
- /* Grab source from ANI or normal Caller*ID */
- num = S_COR(ast_channel_caller(c)->ani.number.valid, ast_channel_caller(c)->ani.number.str,
- S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL));
- ast_callerid_merge(cdr->clid, sizeof(cdr->clid),
- S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, NULL), num, "");
- ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src));
- ast_cdr_setvar(cdr, "dnid", S_OR(ast_channel_dialed(c)->number.str, ""), 0);
+ *ret = NULL;
- if (ast_channel_caller(c)->id.subaddress.valid) {
- ast_cdr_setvar(cdr, "callingsubaddr", S_OR(ast_channel_caller(c)->id.subaddress.str, ""), 0);
+ if (!strcasecmp(name, "clid")) {
+ ast_copy_string(workspace, cdr->clid, workspacelen);
+ } else if (!strcasecmp(name, "src")) {
+ ast_copy_string(workspace, cdr->src, workspacelen);
+ } else if (!strcasecmp(name, "dst")) {
+ ast_copy_string(workspace, cdr->dst, workspacelen);
+ } else if (!strcasecmp(name, "dcontext")) {
+ ast_copy_string(workspace, cdr->dcontext, workspacelen);
+ } else if (!strcasecmp(name, "channel")) {
+ ast_copy_string(workspace, cdr->channel, workspacelen);
+ } else if (!strcasecmp(name, "dstchannel")) {
+ ast_copy_string(workspace, cdr->dstchannel, workspacelen);
+ } else if (!strcasecmp(name, "lastapp")) {
+ ast_copy_string(workspace, cdr->lastapp, workspacelen);
+ } else if (!strcasecmp(name, "lastdata")) {
+ ast_copy_string(workspace, cdr->lastdata, workspacelen);
+ } else if (!strcasecmp(name, "start")) {
+ cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
+ } else if (!strcasecmp(name, "answer")) {
+ cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
+ } else if (!strcasecmp(name, "end")) {
+ cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
+ } else if (!strcasecmp(name, "duration")) {
+ snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
+ } else if (!strcasecmp(name, "billsec")) {
+ snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);
+ } else if (!strcasecmp(name, "disposition")) {
+ if (raw) {
+ snprintf(workspace, workspacelen, "%ld", cdr->disposition);
+ } else {
+ ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
+ }
+ } else if (!strcasecmp(name, "amaflags")) {
+ if (raw) {
+ snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
+ } else {
+ ast_copy_string(workspace, ast_channel_amaflags2string(cdr->amaflags), workspacelen);
+ }
+ } else if (!strcasecmp(name, "accountcode")) {
+ ast_copy_string(workspace, cdr->accountcode, workspacelen);
+ } else if (!strcasecmp(name, "peeraccount")) {
+ ast_copy_string(workspace, cdr->peeraccount, workspacelen);
+ } else if (!strcasecmp(name, "uniqueid")) {
+ ast_copy_string(workspace, cdr->uniqueid, workspacelen);
+ } else if (!strcasecmp(name, "linkedid")) {
+ ast_copy_string(workspace, cdr->linkedid, workspacelen);
+ } else if (!strcasecmp(name, "userfield")) {
+ ast_copy_string(workspace, cdr->userfield, workspacelen);
+ } else if (!strcasecmp(name, "sequence")) {
+ snprintf(workspace, workspacelen, "%d", cdr->sequence);
+ } else if ((varbuf = cdr_format_var_internal(cdr, name))) {
+ ast_copy_string(workspace, varbuf, workspacelen);
+ } else {
+ workspace[0] = '\0';
}
- if (ast_channel_dialed(c)->subaddress.valid) {
- ast_cdr_setvar(cdr, "calledsubaddr", S_OR(ast_channel_dialed(c)->subaddress.str, ""), 0);
+
+ if (!ast_strlen_zero(workspace)) {
+ *ret = workspace;
}
}
-int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c)
+/*
+ * \internal
+ * \brief Callback that finds all CDRs that reference a particular channel
+ */
+static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
{
- for (; cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- set_one_cid(cdr, c);
+ struct cdr_object *cdr = obj;
+ const char *name = arg;
+ if (!(flags & OBJ_KEY)) {
+ return 0;
+ }
+ if (!strcasecmp(cdr->party_a.snapshot->name, name) ||
+ (cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) {
+ return CMP_MATCH;
}
return 0;
}
-static int cdr_seq_inc(struct ast_cdr *cdr)
-{
- return (cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1));
-}
+/* Read Only CDR variables */
+static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
+ "lastapp", "lastdata", "start", "answer", "end", "duration",
+ "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
+ "userfield", "sequence", NULL };
-int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c)
+int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
{
- for ( ; cdr ; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- ast_copy_string(cdr->channel, ast_channel_name(c), sizeof(cdr->channel));
- set_one_cid(cdr, c);
- cdr_seq_inc(cdr);
+ struct cdr_object *cdr;
+ struct cdr_object *it_cdr;
+ struct ao2_iterator *it_cdrs;
+ char *arg = ast_strdupa(channel_name);
+ int x;
- cdr->disposition = (ast_channel_state(c) == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NOANSWER;
- cdr->amaflags = ast_channel_amaflags(c) ? ast_channel_amaflags(c) : ast_default_amaflags;
- ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode));
- ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount));
- /* Destination information */
- ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c),ast_channel_exten(c)), sizeof(cdr->dst));
- ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c),ast_channel_context(c)), sizeof(cdr->dcontext));
- /* Unique call identifier */
- ast_copy_string(cdr->uniqueid, ast_channel_uniqueid(c), sizeof(cdr->uniqueid));
- /* Linked call identifier */
- ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid));
+ for (x = 0; cdr_readonly_vars[x]; x++) {
+ if (!strcasecmp(name, cdr_readonly_vars[x])) {
+ ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!\n", name);
+ return -1;
}
}
- return 0;
-}
-/* Three routines were "fixed" via 10668, and later shown that
- users were depending on this behavior. ast_cdr_end,
- ast_cdr_setvar and ast_cdr_answer are the three routines.
- While most of the other routines would not touch
- LOCKED cdr's, these three routines were designed to
- operate on locked CDR's as a matter of course.
- I now appreciate how this plays with the ForkCDR app,
- which forms these cdr chains in the first place.
- cdr_end is pretty key: all cdrs created are closed
- together. They only vary by start time. Arithmetically,
- users can calculate the subintervals they wish to track. */
+ it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE | OBJ_KEY, cdr_object_select_all_by_channel_cb, arg);
+ if (!it_cdrs) {
+ ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+ return -1;
+ }
-void ast_cdr_end(struct ast_cdr *cdr)
-{
- for ( ; cdr ; cdr = cdr->next) {
- if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- continue;
- check_post(cdr);
- if (ast_tvzero(cdr->end))
- cdr->end = ast_tvnow();
- if (ast_tvzero(cdr->start)) {
- ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "<unknown>"));
- cdr->disposition = AST_CDR_FAILED;
- } else
- cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec;
- if (ast_tvzero(cdr->answer)) {
- if (cdr->disposition == AST_CDR_ANSWERED) {
- ast_log(LOG_WARNING, "CDR on channel '%s' has no answer time but is 'ANSWERED'\n", S_OR(cdr->channel, "<unknown>"));
- cdr->disposition = AST_CDR_FAILED;
+ while ((cdr = ao2_iterator_next(it_cdrs))) {
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ struct varshead *headp = NULL;
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
+ }
+ if (!strcmp(channel_name, it_cdr->party_a.snapshot->name)) {
+ headp = &it_cdr->party_a.variables;
+ } else if (it_cdr->party_b.snapshot && !strcmp(channel_name, it_cdr->party_b.snapshot->name)) {
+ headp = &it_cdr->party_b.variables;
+ }
+ if (headp) {
+ set_variable(headp, name, value);
}
- } else {
- cdr->billsec = cdr->end.tv_sec - cdr->answer.tv_sec;
- if (ast_test_flag(&ast_options, AST_OPT_FLAG_INITIATED_SECONDS))
- cdr->billsec += cdr->end.tv_usec > cdr->answer.tv_usec ? 1 : 0;
}
+ ao2_unlock(cdr);
+ ao2_ref(cdr, -1);
}
+ ao2_iterator_destroy(it_cdrs);
+
+ return 0;
}
-char *ast_cdr_disp2str(int disposition)
+/*!
+ * \brief Format a variable on a \ref cdr_object
+ */
+static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length)
{
- switch (disposition) {
- case AST_CDR_NULL:
- return "NO ANSWER"; /* by default, for backward compatibility */
- case AST_CDR_NOANSWER:
- return "NO ANSWER";
- case AST_CDR_FAILED:
- return "FAILED";
- case AST_CDR_BUSY:
- return "BUSY";
- case AST_CDR_ANSWERED:
- return "ANSWERED";
- case AST_CDR_CONGESTION:
- return "CONGESTION";
+ struct ast_var_t *variable;
+
+ AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) {
+ if (!strcasecmp(name, ast_var_name(variable))) {
+ ast_copy_string(value, ast_var_value(variable), length);
+ return;
+ }
}
- return "UNKNOWN";
+
+ *value = '\0';
}
-/*! Converts AMA flag to printable string
- *
- * \param flag, flags
+/*!
+ * \brief Format one of the standard properties on a \ref cdr_object
*/
-char *ast_cdr_flags2str(int flag)
-{
- switch (flag) {
- case AST_CDR_OMIT:
- return "OMIT";
- case AST_CDR_BILLING:
- return "BILLING";
- case AST_CDR_DOCUMENTATION:
- return "DOCUMENTATION";
+static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length)
+{
+ struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot;
+ struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot;
+
+ if (!strcasecmp(name, "clid")) {
+ ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, "");
+ } else if (!strcasecmp(name, "src")) {
+ ast_copy_string(value, party_a->caller_number, length);
+ } else if (!strcasecmp(name, "dst")) {
+ ast_copy_string(value, party_a->exten, length);
+ } else if (!strcasecmp(name, "dcontext")) {
+ ast_copy_string(value, party_a->context, length);
+ } else if (!strcasecmp(name, "channel")) {
+ ast_copy_string(value, party_a->name, length);
+ } else if (!strcasecmp(name, "dstchannel")) {
+ if (party_b) {
+ ast_copy_string(value, party_b->name, length);
+ } else {
+ ast_copy_string(value, "", length);
+ }
+ } else if (!strcasecmp(name, "lastapp")) {
+ ast_copy_string(value, party_a->appl, length);
+ } else if (!strcasecmp(name, "lastdata")) {
+ ast_copy_string(value, party_a->data, length);
+ } else if (!strcasecmp(name, "start")) {
+ cdr_get_tv(cdr_obj->start, NULL, value, length);
+ } else if (!strcasecmp(name, "answer")) {
+ cdr_get_tv(cdr_obj->answer, NULL, value, length);
+ } else if (!strcasecmp(name, "end")) {
+ cdr_get_tv(cdr_obj->end, NULL, value, length);
+ } else if (!strcasecmp(name, "duration")) {
+ snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj));
+ } else if (!strcasecmp(name, "billsec")) {
+ snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
+ } else if (!strcasecmp(name, "disposition")) {
+ snprintf(value, length, "%d", cdr_obj->disposition);
+ } else if (!strcasecmp(name, "amaflags")) {
+ snprintf(value, length, "%d", party_a->amaflags);
+ } else if (!strcasecmp(name, "accountcode")) {
+ ast_copy_string(value, party_a->accountcode, length);
+ } else if (!strcasecmp(name, "peeraccount")) {
+ if (party_b) {
+ ast_copy_string(value, party_b->accountcode, length);
+ } else {
+ ast_copy_string(value, "", length);
+ }
+ } else if (!strcasecmp(name, "uniqueid")) {
+ ast_copy_string(value, party_a->uniqueid, length);
+ } else if (!strcasecmp(name, "linkedid")) {
+ ast_copy_string(value, cdr_obj->linkedid, length);
+ } else if (!strcasecmp(name, "userfield")) {
+ ast_copy_string(value, cdr_obj->party_a.userfield, length);
+ } else if (!strcasecmp(name, "sequence")) {
+ snprintf(value, length, "%d", cdr_obj->sequence);
+ } else {
+ return 1;
}
- return "Unknown";
+
+ return 0;
}
-int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
+int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
{
- struct ast_cdr *cdr = ast_channel_cdr(chan);
- const char *old_acct = "";
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *cdr_obj;
- if (!ast_strlen_zero(ast_channel_accountcode(chan))) {
- old_acct = ast_strdupa(ast_channel_accountcode(chan));
+ if (!cdr) {
+ ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+ return 1;
}
- ast_channel_accountcode_set(chan, account);
- for ( ; cdr ; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- ast_copy_string(cdr->accountcode, ast_channel_accountcode(chan), sizeof(cdr->accountcode));
- }
+ if (ast_strlen_zero(name)) {
+ return 1;
}
- /*** DOCUMENTATION
- <managerEventInstance>
- <synopsis>Raised when a CDR's AccountCode is changed.</synopsis>
- </managerEventInstance>
- ***/
- ast_manager_event(chan, EVENT_FLAG_CALL, "NewAccountCode",
- "Channel: %s\r\n"
- "Uniqueid: %s\r\n"
- "AccountCode: %s\r\n"
- "OldAccountCode: %s\r\n",
- ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_accountcode(chan), old_acct);
+
+ ao2_lock(cdr);
+
+ cdr_obj = cdr->last;
+
+ if (cdr_object_format_property(cdr_obj, name, value, length)) {
+ /* Property failed; attempt variable */
+ cdr_object_format_var_internal(cdr_obj, name, value, length);
+ }
+ ao2_unlock(cdr);
return 0;
}
-int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account)
+int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
{
- struct ast_cdr *cdr = ast_channel_cdr(chan);
- const char *old_acct = "";
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *it_cdr;
+ struct ast_var_t *variable;
+ const char *var;
+ RAII_VAR(char *, workspace, ast_malloc(256), ast_free);
+ int total = 0, x = 0, i;
- if (!ast_strlen_zero(ast_channel_peeraccount(chan))) {
- old_acct = ast_strdupa(ast_channel_peeraccount(chan));
+ if (!workspace) {
+ return 1;
}
- ast_channel_peeraccount_set(chan, account);
- for ( ; cdr ; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(chan), sizeof(cdr->peeraccount));
- }
+ if (!cdr) {
+ ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+ return 1;
}
- /*** DOCUMENTATION
- <managerEventInstance>
- <synopsis>Raised when a CDR's PeerAccount is changed.</synopsis>
- </managerEventInstance>
- ***/
- ast_manager_event(chan, EVENT_FLAG_CALL, "NewPeerAccount",
- "Channel: %s\r\n"
- "Uniqueid: %s\r\n"
- "PeerAccount: %s\r\n"
- "OldPeerAccount: %s\r\n",
- ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_peeraccount(chan), old_acct);
- return 0;
-}
+ ast_str_reset(*buf);
-int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag)
-{
- struct ast_cdr *cdr;
- int newflag = ast_cdr_amaflags2int(flag);
- if (newflag) {
- for (cdr = ast_channel_cdr(chan); cdr; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- cdr->amaflags = newflag;
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (++x > 1)
+ ast_str_append(buf, 0, "\n");
+
+ AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) {
+ if (!(var = ast_var_name(variable))) {
+ continue;
+ }
+
+ if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variable), ""), sep) < 0) {
+ ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+ break;
+ }
+
+ total++;
+ }
+
+ for (i = 0; cdr_readonly_vars[i]; i++) {
+ /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
+ workspace[0] = 0;
+ cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace));
+
+ if (!ast_strlen_zero(workspace)
+ && ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) {
+ ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+ break;
}
+ total++;
}
}
- return 0;
+ return total;
}
-int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield)
+void ast_cdr_free(struct ast_cdr *cdr)
{
- struct ast_cdr *cdr = ast_channel_cdr(chan);
+ while (cdr) {
+ struct ast_cdr *next = cdr->next;
- for ( ; cdr ; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield));
+ free_variables(&cdr->varshead);
+ ast_free(cdr);
+ cdr = next;
}
-
- return 0;
}
-int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield)
+struct ast_cdr *ast_cdr_alloc(void)
{
- struct ast_cdr *cdr = ast_channel_cdr(chan);
+ struct ast_cdr *x;
- for ( ; cdr ; cdr = cdr->next) {
- int len = strlen(cdr->userfield);
+ x = ast_calloc(1, sizeof(*x));
+ return x;
+}
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
- ast_copy_string(cdr->userfield + len, userfield, sizeof(cdr->userfield) - len);
+const char *ast_cdr_disp2str(int disposition)
+{
+ switch (disposition) {
+ case AST_CDR_NULL:
+ return "NO ANSWER"; /* by default, for backward compatibility */
+ case AST_CDR_NOANSWER:
+ return "NO ANSWER";
+ case AST_CDR_FAILED:
+ return "FAILED";
+ case AST_CDR_BUSY:
+ return "BUSY";
+ case AST_CDR_ANSWERED:
+ return "ANSWERED";
+ case AST_CDR_CONGESTION:
+ return "CONGESTION";
}
+ return "UNKNOWN";
+}
+struct party_b_userfield_update {
+ const char *channel_name;
+ const char *userfield;
+};
+
+/*! \brief Callback used to update the userfield on Party B on all CDRs */
+static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flags)
+{
+ struct cdr_object *cdr = obj;
+ struct party_b_userfield_update *info = arg;
+ struct cdr_object *it_cdr;
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
+ }
+ if (it_cdr->party_b.snapshot
+ && !strcmp(it_cdr->party_b.snapshot->name, info->channel_name)) {
+ strcpy(it_cdr->party_b.userfield, info->userfield);
+ }
+ }
return 0;
}
-int ast_cdr_update(struct ast_channel *c)
+void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
{
- struct ast_cdr *cdr = ast_channel_cdr(c);
-
- for ( ; cdr ; cdr = cdr->next) {
- if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- set_one_cid(cdr, c);
-
- /* Copy account code et-al */
- ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode));
- ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount));
- ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid));
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct party_b_userfield_update party_b_info = {
+ .channel_name = channel_name,
+ .userfield = userfield,
+ };
+ struct cdr_object *it_cdr;
- /* Destination information */ /* XXX privilege macro* ? */
- ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c), ast_channel_exten(c)), sizeof(cdr->dst));
- ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c), ast_channel_context(c)), sizeof(cdr->dcontext));
+ /* Handle Party A */
+ if (cdr) {
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
+ }
+ strcpy(it_cdr->party_a.userfield, userfield);
}
+ ao2_unlock(cdr);
}
- return 0;
-}
+ /* Handle Party B */
+ ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
+ cdr_object_update_party_b_userfield_cb,
+ &party_b_info);
-int ast_cdr_amaflags2int(const char *flag)
-{
- if (!strcasecmp(flag, "default"))
- return 0;
- if (!strcasecmp(flag, "omit"))
- return AST_CDR_OMIT;
- if (!strcasecmp(flag, "billing"))
- return AST_CDR_BILLING;
- if (!strcasecmp(flag, "documentation"))
- return AST_CDR_DOCUMENTATION;
- return -1;
}
static void post_cdr(struct ast_cdr *cdr)
{
- struct ast_cdr_beitem *i;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct cdr_beitem *i;
- for ( ; cdr ; cdr = cdr->next) {
- if (!unanswered && cdr->disposition < AST_CDR_ANSWERED && (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
- /* For people, who don't want to see unanswered single-channel events */
- ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
+ for (; cdr ; cdr = cdr->next) {
+ /* For people, who don't want to see unanswered single-channel events */
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
+ cdr->disposition < AST_CDR_ANSWERED &&
+ (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
continue;
}
- /* don't post CDRs that are for dialed channels unless those
- * channels were originated from asterisk (pbx_spool, manager,
- * cli) */
- if (ast_test_flag(cdr, AST_CDR_FLAG_DIALED) && !ast_test_flag(cdr, AST_CDR_FLAG_ORIGINATED)) {
- ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
+ if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
continue;
}
-
- check_post(cdr);
- ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
- if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED))
- continue;
AST_RWLIST_RDLOCK(&be_list);
AST_RWLIST_TRAVERSE(&be_list, i, list) {
i->be(cdr);
@@ -1207,88 +2965,170 @@ static void post_cdr(struct ast_cdr *cdr)
}
}
-void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
+int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
{
- struct ast_cdr *duplicate;
- struct ast_flags flags = { 0 };
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *it_cdr;
- if (_flags)
- ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
+ if (!cdr) {
+ return -1;
+ }
- for ( ; cdr ; cdr = cdr->next) {
- /* Detach if post is requested */
- if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
- if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) {
- ast_cdr_end(cdr);
- if ((duplicate = ast_cdr_dup_unique_swap(cdr))) {
- ast_cdr_detach(duplicate);
- }
- ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
- }
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
+ }
+ ast_set_flag(&it_cdr->flags, option);
+ }
+ ao2_unlock(cdr);
- /* enable CDR only */
- if (ast_test_flag(&flags, AST_CDR_FLAG_POST_ENABLE)) {
- ast_clear_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
- continue;
- }
+ return 0;
+}
- /* clear variables */
- if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) {
- ast_cdr_free_vars(cdr, 0);
- }
+int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
+{
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *it_cdr;
+
+ if (!cdr) {
+ return -1;
+ }
- /* Reset to initial state */
- ast_clear_flag(cdr, AST_FLAGS_ALL);
- memset(&cdr->start, 0, sizeof(cdr->start));
- memset(&cdr->end, 0, sizeof(cdr->end));
- memset(&cdr->answer, 0, sizeof(cdr->answer));
- cdr->billsec = 0;
- cdr->duration = 0;
- ast_cdr_start(cdr);
- cdr->disposition = AST_CDR_NOANSWER;
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
}
+ ast_clear_flag(&it_cdr->flags, option);
}
+ ao2_unlock(cdr);
+
+ return 0;
}
-void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
+int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
{
- struct ast_flags flags = { 0 };
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct ast_var_t *vardata;
+ struct cdr_object *it_cdr;
+
+ if (!cdr) {
+ return -1;
+ }
- if (_flags)
- ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
+ ao2_lock(cdr);
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ /* clear variables */
+ if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+ while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
+ ast_var_delete(vardata);
+ }
+ if (cdr->party_b.snapshot) {
+ while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_b.variables, entries))) {
+ ast_var_delete(vardata);
+ }
+ }
+ }
- /* Reset to initial state */
- if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) { /* But do NOT lose the NoCDR() setting */
- ast_clear_flag(cdr, AST_FLAGS_ALL);
- ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
- } else {
- ast_clear_flag(cdr, AST_FLAGS_ALL);
+ /* Reset to initial state */
+ memset(&it_cdr->start, 0, sizeof(it_cdr->start));
+ memset(&it_cdr->end, 0, sizeof(it_cdr->end));
+ memset(&it_cdr->answer, 0, sizeof(it_cdr->answer));
+ it_cdr->start = ast_tvnow();
+ cdr_object_check_party_a_answer(it_cdr);
}
+ ao2_unlock(cdr);
- memset(&cdr->start, 0, sizeof(cdr->start));
- memset(&cdr->end, 0, sizeof(cdr->end));
- memset(&cdr->answer, 0, sizeof(cdr->answer));
- cdr->billsec = 0;
- cdr->duration = 0;
- ast_cdr_start(cdr);
- cdr->disposition = AST_CDR_NULL;
+ return 0;
}
-struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr)
+int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
{
- struct ast_cdr *ret;
+ RAII_VAR(struct cdr_object *, cdr,
+ ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+ ao2_cleanup);
+ struct cdr_object *new_cdr;
+ struct cdr_object *it_cdr;
+ struct cdr_object *cdr_obj;
- if (cdr) {
- ret = cdr;
+ if (!cdr) {
+ return -1;
+ }
- while (cdr->next)
- cdr = cdr->next;
- cdr->next = newcdr;
- } else {
- ret = newcdr;
+ {
+ SCOPED_AO2LOCK(lock, cdr);
+ cdr_obj = cdr->last;
+ if (cdr_obj->fn_table == &finalized_state_fn_table) {
+ /* If the last CDR in the chain is finalized, don't allow a fork -
+ * things are already dying at this point
+ */
+ ast_log(AST_LOG_ERROR, "FARK\n");
+ return -1;
+ }
+
+ /* Copy over the basic CDR information. The Party A information is
+ * copied over automatically as part of the append
+ */
+ ast_debug(1, "Forking CDR for channel %s\n", cdr->party_a.snapshot->name);
+ new_cdr = cdr_object_create_and_append(cdr);
+ if (!new_cdr) {
+ return -1;
+ }
+ new_cdr->fn_table = cdr_obj->fn_table;
+ ast_string_field_set(new_cdr, bridge, cdr->bridge);
+ new_cdr->flags = cdr->flags;
+
+ /* If there's a Party B, copy it over as well */
+ if (cdr_obj->party_b.snapshot) {
+ new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot;
+ ao2_ref(new_cdr->party_b.snapshot, +1);
+ strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield);
+ new_cdr->party_b.flags = cdr_obj->party_b.flags;
+ if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+ copy_variables(&new_cdr->party_b.variables, &cdr_obj->party_b.variables);
+ }
+ }
+ new_cdr->start = cdr_obj->start;
+ new_cdr->answer = cdr_obj->answer;
+
+ /* Modify the times based on the flags passed in */
+ if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER)
+ && new_cdr->party_a.snapshot->state == AST_STATE_UP) {
+ new_cdr->answer = ast_tvnow();
+ }
+ if (ast_test_flag(options, AST_CDR_FLAG_RESET)) {
+ new_cdr->answer = ast_tvnow();
+ new_cdr->start = ast_tvnow();
+ }
+
+ /* Create and append, by default, copies over the variables */
+ if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+ free_variables(&new_cdr->party_a.variables);
+ }
+
+ /* Finalize any current CDRs */
+ if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
+ for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) {
+ if (it_cdr->fn_table == &finalized_state_fn_table) {
+ continue;
+ }
+ /* Force finalization on the CDR. This will bypass any checks for
+ * end before 'h' extension.
+ */
+ cdr_object_finalize(it_cdr);
+ cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
+ }
+ }
}
- return ret;
+ return 0;
}
/*! \note Don't call without cdr_batch_lock */
@@ -1313,8 +3153,8 @@ static int init_batch(void)
static void *do_batch_backend_process(void *data)
{
- struct ast_cdr_batch_item *processeditem;
- struct ast_cdr_batch_item *batchitem = data;
+ struct cdr_batch_item *processeditem;
+ struct cdr_batch_item *batchitem = data;
/* Push each CDR into storage mechanism(s) and free all the memory */
while (batchitem) {
@@ -1328,14 +3168,16 @@ static void *do_batch_backend_process(void *data)
return NULL;
}
-void ast_cdr_submit_batch(int do_shutdown)
+static void cdr_submit_batch(int do_shutdown)
{
- struct ast_cdr_batch_item *oldbatchitems = NULL;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ struct cdr_batch_item *oldbatchitems = NULL;
pthread_t batch_post_thread = AST_PTHREADT_NULL;
/* if there's no batch, or no CDRs in the batch, then there's nothing to do */
- if (!batch || !batch->head)
+ if (!batch || !batch->head) {
return;
+ }
/* move the old CDRs aside, and prepare a new CDR batch */
ast_mutex_lock(&cdr_batch_lock);
@@ -1345,7 +3187,7 @@ void ast_cdr_submit_batch(int do_shutdown)
/* if configured, spawn a new thread to post these CDRs,
also try to save as much as possible if we are shutting down safely */
- if (batchscheduleronly || do_shutdown) {
+ if (ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) || do_shutdown) {
ast_debug(1, "CDR single-threaded batch processing begins now\n");
do_batch_backend_process(oldbatchitems);
} else {
@@ -1360,10 +3202,12 @@ void ast_cdr_submit_batch(int do_shutdown)
static int submit_scheduled_batch(const void *data)
{
- ast_cdr_submit_batch(0);
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ cdr_submit_batch(0);
/* manually reschedule from this point in time */
+
ast_mutex_lock(&cdr_sched_lock);
- cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
+ cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL);
ast_mutex_unlock(&cdr_sched_lock);
/* returning zero so the scheduler does not automatically reschedule */
return 0;
@@ -1386,25 +3230,26 @@ static void submit_unscheduled_batch(void)
ast_mutex_unlock(&cdr_pending_lock);
}
-void ast_cdr_detach(struct ast_cdr *cdr)
+static void cdr_detach(struct ast_cdr *cdr)
{
- struct ast_cdr_batch_item *newtail;
+ struct cdr_batch_item *newtail;
int curr;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
int submit_batch = 0;
- if (!cdr)
+ if (!cdr) {
return;
+ }
/* maybe they disabled CDR stuff completely, so just drop it */
- if (!enabled) {
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
ast_debug(1, "Dropping CDR !\n");
- ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
ast_cdr_free(cdr);
return;
}
/* post stuff immediately if we are not in batch mode, this is legacy behaviour */
- if (!batchmode) {
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
post_cdr(cdr);
ast_cdr_free(cdr);
return;
@@ -1436,7 +3281,7 @@ void ast_cdr_detach(struct ast_cdr *cdr)
curr = batch->size++;
/* if we have enough stuff to post, then do it */
- if (curr >= (batchsize - 1)) {
+ if (curr >= (mod_cfg->general->batch_settings.size - 1)) {
submit_batch = 1;
}
ast_mutex_unlock(&cdr_batch_lock);
@@ -1473,11 +3318,40 @@ static void *do_cdr(void *data)
return NULL;
}
+static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "cdr set debug [on|off]";
+ e->usage = "Enable or disable extra debugging in the CDR Engine";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ if (!strcmp(a->argv[3], "on") && !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
+ ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG);
+ ast_cli(a->fd, "CDR debugging enabled\n");
+ } else if (!strcmp(a->argv[3], "off") && ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
+ ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG);
+ ast_cli(a->fd, "CDR debugging disabled\n");
+ }
+
+ return CLI_SUCCESS;
+}
+
static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- struct ast_cdr_beitem *beitem=NULL;
- int cnt=0;
- long nextbatchtime=0;
+ struct cdr_beitem *beitem = NULL;
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ int cnt = 0;
+ long nextbatchtime = 0;
switch (cmd) {
case CLI_INIT:
@@ -1496,23 +3370,23 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
ast_cli(a->fd, "\n");
ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
ast_cli(a->fd, "----------------------------------\n");
- ast_cli(a->fd, " Logging: %s\n", enabled ? "Enabled" : "Disabled");
- ast_cli(a->fd, " Mode: %s\n", batchmode ? "Batch" : "Simple");
- if (enabled) {
- ast_cli(a->fd, " Log unanswered calls: %s\n", unanswered ? "Yes" : "No");
- ast_cli(a->fd, " Log congestion: %s\n\n", congestion ? "Yes" : "No");
- if (batchmode) {
+ ast_cli(a->fd, " Logging: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ? "Enabled" : "Disabled");
+ ast_cli(a->fd, " Mode: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE) ? "Batch" : "Simple");
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+ ast_cli(a->fd, " Log unanswered calls: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) ? "Yes" : "No");
+ ast_cli(a->fd, " Log congestion: %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION) ? "Yes" : "No");
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
ast_cli(a->fd, "* Batch Mode Settings\n");
ast_cli(a->fd, " -------------------\n");
if (batch)
cnt = batch->size;
if (cdr_sched > -1)
nextbatchtime = ast_sched_when(sched, cdr_sched);
- ast_cli(a->fd, " Safe shutdown: %s\n", batchsafeshutdown ? "Enabled" : "Disabled");
- ast_cli(a->fd, " Threading model: %s\n", batchscheduleronly ? "Scheduler only" : "Scheduler plus separate threads");
+ ast_cli(a->fd, " Safe shutdown: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN) ? "Enabled" : "Disabled");
+ ast_cli(a->fd, " Threading model: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) ? "Scheduler only" : "Scheduler plus separate threads");
ast_cli(a->fd, " Current batch size: %d record%s\n", cnt, ESS(cnt));
- ast_cli(a->fd, " Maximum batch size: %d record%s\n", batchsize, ESS(batchsize));
- ast_cli(a->fd, " Maximum batch time: %d second%s\n", batchtime, ESS(batchtime));
+ ast_cli(a->fd, " Maximum batch size: %d record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
+ ast_cli(a->fd, " Maximum batch time: %d second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
ast_cli(a->fd, " Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime));
}
ast_cli(a->fd, "* Registered Backends\n");
@@ -1555,148 +3429,163 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data");
static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status");
+static struct ast_cli_entry cli_debug = AST_CLI_DEFINE(handle_cli_debug, "Enable debugging");
+
-static void do_reload(int reload)
+/*!
+ * \brief This dispatches *all* \ref cdr_objects. It should only be used during
+ * shutdown, so that we get billing records for everything that we can.
+ */
+static int cdr_object_dispatch_all_cb(void *obj, void *arg, int flags)
{
- struct ast_config *config;
- struct ast_variable *v;
- int cfg_size;
- int cfg_time;
- int was_enabled;
- int was_batchmode;
- struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ struct cdr_object *cdr = obj;
+ struct cdr_object *it_cdr;
- if ((config = ast_config_load2("cdr.conf", "cdr", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
- return;
+ for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+ cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
}
+ cdr_object_dispatch(cdr);
- ast_mutex_lock(&cdr_batch_lock);
-
- was_enabled = enabled;
- was_batchmode = batchmode;
-
- batchsize = BATCH_SIZE_DEFAULT;
- batchtime = BATCH_TIME_DEFAULT;
- batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT;
- batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT;
- enabled = ENABLED_DEFAULT;
- batchmode = BATCHMODE_DEFAULT;
- unanswered = UNANSWERED_DEFAULT;
- congestion = CONGESTION_DEFAULT;
+ return 0;
+}
- if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
- ast_mutex_unlock(&cdr_batch_lock);
+static void finalize_batch_mode(void)
+{
+ if (cdr_thread == AST_PTHREADT_NULL) {
return;
}
+ /* wake up the thread so it will exit */
+ pthread_cancel(cdr_thread);
+ pthread_kill(cdr_thread, SIGURG);
+ pthread_join(cdr_thread, NULL);
+ cdr_thread = AST_PTHREADT_NULL;
+ ast_cond_destroy(&cdr_pending_cond);
+ ast_cli_unregister(&cli_submit);
+ ast_cdr_engine_term();
+}
- /* don't run the next scheduled CDR posting while reloading */
- ast_mutex_lock(&cdr_sched_lock);
- AST_SCHED_DEL(sched, cdr_sched);
- ast_mutex_unlock(&cdr_sched_lock);
+static int process_config(int reload)
+{
+ RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup);
- for (v = ast_variable_browse(config, "general"); v; v = v->next) {
- if (!strcasecmp(v->name, "enable")) {
- enabled = ast_true(v->value);
- } else if (!strcasecmp(v->name, "unanswered")) {
- unanswered = ast_true(v->value);
- } else if (!strcasecmp(v->name, "congestion")) {
- congestion = ast_true(v->value);
- } else if (!strcasecmp(v->name, "batch")) {
- batchmode = ast_true(v->value);
- } else if (!strcasecmp(v->name, "scheduleronly")) {
- batchscheduleronly = ast_true(v->value);
- } else if (!strcasecmp(v->name, "safeshutdown")) {
- batchsafeshutdown = ast_true(v->value);
- } else if (!strcasecmp(v->name, "size")) {
- if (sscanf(v->value, "%30d", &cfg_size) < 1) {
- ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
- } else if (cfg_size < 0) {
- ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size);
- } else {
- batchsize = cfg_size;
- }
- } else if (!strcasecmp(v->name, "time")) {
- if (sscanf(v->value, "%30d", &cfg_time) < 1) {
- ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
- } else if (cfg_time < 0) {
- ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time);
- } else {
- batchtime = cfg_time;
- }
- } else if (!strcasecmp(v->name, "endbeforehexten")) {
- ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN);
- } else if (!strcasecmp(v->name, "initiatedseconds")) {
- ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INITIATED_SECONDS);
+ if (!reload) {
+ if (aco_info_init(&cfg_info)) {
+ return 1;
}
- }
- if (enabled && !batchmode) {
- ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
- } else if (enabled && batchmode) {
- ast_mutex_lock(&cdr_sched_lock);
- cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
- ast_mutex_unlock(&cdr_sched_lock);
- ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime);
- } else {
- ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
- }
-
- /* if this reload enabled the CDR batch mode, create the background thread
- if it does not exist */
- if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) {
- ast_cond_init(&cdr_pending_cond, NULL);
- if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) {
- ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
- ast_mutex_lock(&cdr_sched_lock);
- AST_SCHED_DEL(sched, cdr_sched);
- ast_mutex_unlock(&cdr_sched_lock);
- } else {
- ast_cli_register(&cli_submit);
- ast_register_atexit(ast_cdr_engine_term);
- }
- /* if this reload disabled the CDR and/or batch mode and there is a background thread,
- kill it */
- } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) {
- /* wake up the thread so it will exit */
- pthread_cancel(cdr_thread);
- pthread_kill(cdr_thread, SIGURG);
- pthread_join(cdr_thread, NULL);
- cdr_thread = AST_PTHREADT_NULL;
- ast_cond_destroy(&cdr_pending_cond);
- ast_cli_unregister(&cli_submit);
- ast_unregister_atexit(ast_cdr_engine_term);
- /* if leaving batch mode, then post the CDRs in the batch,
- and don't reschedule, since we are stopping CDR logging */
- if (!batchmode && was_batchmode) {
- ast_cdr_engine_term();
+ aco_option_register(&cfg_info, "enable", ACO_EXACT, general_options, DEFAULT_ENABLED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_ENABLED);
+ aco_option_register(&cfg_info, "debug", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_DEBUG);
+ aco_option_register(&cfg_info, "unanswered", ACO_EXACT, general_options, DEFAULT_UNANSWERED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_UNANSWERED);
+ aco_option_register(&cfg_info, "congestion", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_CONGESTION);
+ aco_option_register(&cfg_info, "batch", ACO_EXACT, general_options, DEFAULT_BATCHMODE, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_BATCHMODE);
+ aco_option_register(&cfg_info, "endbeforehexten", ACO_EXACT, general_options, DEFAULT_END_BEFORE_H_EXTEN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_END_BEFORE_H_EXTEN);
+ aco_option_register(&cfg_info, "initiatedseconds", ACO_EXACT, general_options, DEFAULT_INITIATED_SECONDS, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_INITIATED_SECONDS);
+ aco_option_register(&cfg_info, "scheduleronly", ACO_EXACT, general_options, DEFAULT_BATCH_SCHEDULER_ONLY, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SCHEDULER_ONLY);
+ aco_option_register(&cfg_info, "safeshutdown", ACO_EXACT, general_options, DEFAULT_BATCH_SAFE_SHUTDOWN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SAFE_SHUTDOWN);
+ aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE);
+ aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 0, MAX_BATCH_TIME);
+ }
+
+ if (aco_process_config(&cfg_info, reload)) {
+ if (!mod_cfg) {
+ return 1;
+ }
+ /* If we couldn't process the configuration and this wasn't a reload,
+ * create a default config
+ */
+ if (!reload && !(aco_set_defaults(&general_option, "general", mod_cfg->general))) {
+ ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n");
+ ao2_global_obj_replace(module_configs, mod_cfg);
+ return 0;
}
+ return 1;
}
- ast_mutex_unlock(&cdr_batch_lock);
- ast_config_destroy(config);
+ if (reload) {
+ manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n");
+ }
+ return 0;
}
static void cdr_engine_shutdown(void)
{
- if (cdr_thread != AST_PTHREADT_NULL) {
- /* wake up the thread so it will exit */
- pthread_cancel(cdr_thread);
- pthread_kill(cdr_thread, SIGURG);
- pthread_join(cdr_thread, NULL);
- cdr_thread = AST_PTHREADT_NULL;
- ast_cond_destroy(&cdr_pending_cond);
- }
- ast_cli_unregister(&cli_submit);
-
+ ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb,
+ NULL);
+ finalize_batch_mode();
+ aco_info_destroy(&cfg_info);
ast_cli_unregister(&cli_status);
+ ast_cli_unregister(&cli_debug);
ast_sched_context_destroy(sched);
sched = NULL;
ast_free(batch);
batch = NULL;
+
+ ao2_ref(active_cdrs_by_channel, -1);
+ ao2_ref(active_cdrs_by_bridge, -1);
+}
+
+static void cdr_enable_batch_mode(struct ast_cdr_config *config)
+{
+ SCOPED_LOCK(batch, &cdr_batch_lock, ast_mutex_lock, ast_mutex_unlock);
+
+ /* Only create the thread level portions once */
+ if (cdr_thread == AST_PTHREADT_NULL) {
+ ast_cond_init(&cdr_pending_cond, NULL);
+ if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) {
+ ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
+ return;
+ }
+ ast_cli_register(&cli_submit);
+ }
+
+ /* Kill the currently scheduled item */
+ AST_SCHED_DEL(sched, cdr_sched);
+ cdr_sched = ast_sched_add(sched, config->batch_settings.time * 1000, submit_scheduled_batch, NULL);
+ ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n",
+ config->batch_settings.size, config->batch_settings.time);
}
int ast_cdr_engine_init(void)
{
+ RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
+
+ if (process_config(0)) {
+ return -1;
+ }
+
+ /* The prime here should be the same as the channel container */
+ active_cdrs_by_channel = ao2_container_alloc(51, cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
+ if (!active_cdrs_by_channel) {
+ return -1;
+ }
+
+ active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn);
+ if (!active_cdrs_by_bridge) {
+ return -1;
+ }
+
+ cdr_topic = stasis_topic_create("cdr_engine");
+ if (!cdr_topic) {
+ return -1;
+ }
+
+ channel_subscription = stasis_forward_all(stasis_caching_get_topic(ast_channel_topic_all_cached()), cdr_topic);
+ if (!channel_subscription) {
+ return -1;
+ }
+ bridge_subscription = stasis_forward_all(stasis_caching_get_topic(ast_bridge_topic_all_cached()), cdr_topic);
+ if (!bridge_subscription) {
+ return -1;
+ }
+ stasis_router = stasis_message_router_create(cdr_topic);
+ if (!stasis_router) {
+ return -1;
+ }
+ stasis_message_router_add(stasis_router, stasis_cache_update_type(), handle_channel_cache_message, NULL);
+ stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
+ stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
+ stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
+
sched = ast_sched_context_create();
if (!sched) {
ast_log(LOG_ERROR, "Unable to create schedule context.\n");
@@ -1704,69 +3593,70 @@ int ast_cdr_engine_init(void)
}
ast_cli_register(&cli_status);
- do_reload(0);
+ ast_cli_register(&cli_debug);
ast_register_atexit(cdr_engine_shutdown);
+ mod_cfg = ao2_global_obj_ref(module_configs);
+
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+ cdr_enable_batch_mode(mod_cfg->general);
+ } else {
+ ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+ }
+ } else {
+ ast_log(LOG_NOTICE, "CDR logging disabled.\n");
+ }
+
return 0;
}
-/* \note This actually gets called a couple of times at shutdown. Once, before we start
- hanging up channels, and then again, after the channel hangup timeout expires */
void ast_cdr_engine_term(void)
{
- ast_cdr_submit_batch(batchsafeshutdown);
-}
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-int ast_cdr_engine_reload(void)
-{
- do_reload(1);
- return 0;
+ /* Since this is called explicitly during process shutdown, we might not have ever
+ * been initialized. If so, the config object will be NULL.
+ */
+ if (!mod_cfg) {
+ return;
+ }
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+ return;
+ }
+ cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
}
-int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur)
+int ast_cdr_engine_reload(void)
{
- struct ast_cdr *tmpcdr;
- struct ast_data *level;
- struct ast_var_t *variables;
- const char *var, *val;
- int x = 1, i;
- char workspace[256];
- char *tmp;
+ RAII_VAR(struct module_config *, old_mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
- if (!cdr) {
+ if (process_config(1)) {
return -1;
}
- for (tmpcdr = cdr; tmpcdr; tmpcdr = (recur ? tmpcdr->next : NULL)) {
- level = ast_data_add_node(tree, "level");
- if (!level) {
- continue;
- }
-
- ast_data_add_int(level, "level_number", x);
+ mod_cfg = ao2_global_obj_ref(module_configs);
- AST_LIST_TRAVERSE(&tmpcdr->varshead, variables, entries) {
- if (variables && (var = ast_var_name(variables)) &&
- (val = ast_var_value(variables)) && !ast_strlen_zero(var)
- && !ast_strlen_zero(val)) {
- ast_data_add_str(level, var, val);
- } else {
- break;
- }
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ||
+ !(ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE))) {
+ /* If batch mode used to be enabled, finalize the batch */
+ if (ast_test_flag(&old_mod_cfg->general->settings, CDR_BATCHMODE)) {
+ finalize_batch_mode();
}
+ }
- for (i = 0; cdr_readonly_vars[i]; i++) {
- workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
- ast_cdr_getvar(tmpcdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
- if (!tmp) {
- continue;
- }
- ast_data_add_str(level, cdr_readonly_vars[i], tmp);
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+ if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+ ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+ } else {
+ cdr_enable_batch_mode(mod_cfg->general);
}
-
- x++;
+ } else {
+ ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
}
return 0;
}
+