summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
Diffstat (limited to 'main')
-rw-r--r--main/asterisk.c26
-rw-r--r--main/bridging.c77
-rw-r--r--main/bridging_basic.c3
-rw-r--r--main/cdr.c3942
-rw-r--r--main/cel.c20
-rw-r--r--main/channel.c319
-rw-r--r--main/channel_internal_api.c81
-rw-r--r--main/cli.c10
-rw-r--r--main/dial.c15
-rw-r--r--main/features.c4
-rw-r--r--main/manager.c10
-rw-r--r--main/manager_channels.c34
-rw-r--r--main/pbx.c131
-rw-r--r--main/stasis.c7
-rw-r--r--main/stasis_cache.c3
-rw-r--r--main/stasis_channels.c41
-rw-r--r--main/test.c66
-rw-r--r--main/utils.c9
18 files changed, 3334 insertions, 1464 deletions
diff --git a/main/asterisk.c b/main/asterisk.c
index 99da006a5..871cd979a 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -643,7 +643,7 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c
ast_cli(a->fd, " -------------\n");
ast_cli(a->fd, " Manager (AMI): %s\n", check_manager_enabled() ? "Enabled" : "Disabled");
ast_cli(a->fd, " Web Manager (AMI/HTTP): %s\n", check_webmanager_enabled() ? "Enabled" : "Disabled");
- ast_cli(a->fd, " Call data records: %s\n", check_cdr_enabled() ? "Enabled" : "Disabled");
+ ast_cli(a->fd, " Call data records: %s\n", ast_cdr_is_enabled() ? "Enabled" : "Disabled");
ast_cli(a->fd, " Realtime Architecture (ARA): %s\n", ast_realtime_enabled() ? "Enabled" : "Disabled");
/*! \todo we could check musiconhold, voicemail, smdi, adsi, queues */
@@ -4318,50 +4318,50 @@ int main(int argc, char *argv[])
ast_http_init(); /* Start the HTTP server, if needed */
- if (ast_cdr_engine_init()) {
+ if (ast_indications_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_device_state_engine_init()) {
+ if (ast_features_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_presence_state_engine_init()) {
+ if (ast_bridging_init()) {
printf("%s", term_quit());
exit(1);
}
- ast_dsp_init();
- ast_udptl_init();
-
- if (ast_image_init()) {
+ if (ast_cdr_engine_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_file_init()) {
+ if (ast_device_state_engine_init()) {
printf("%s", term_quit());
exit(1);
}
- if (load_pbx()) {
+ if (ast_presence_state_engine_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_indications_init()) {
+ ast_dsp_init();
+ ast_udptl_init();
+
+ if (ast_image_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_features_init()) {
+ if (ast_file_init()) {
printf("%s", term_quit());
exit(1);
}
- if (ast_bridging_init()) {
+ if (load_pbx()) {
printf("%s", term_quit());
exit(1);
}
diff --git a/main/bridging.c b/main/bridging.c
index 730050473..93e4ec2d8 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -292,6 +292,75 @@ int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action)
return 0;
}
+void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+ struct ast_bridge_channel *other = NULL;
+
+ AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+ if (other == swap) {
+ continue;
+ }
+
+ if (!ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan)) && ast_strlen_zero(ast_channel_peeraccount(other->chan))) {
+ ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
+ ast_channel_accountcode(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+ ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
+ }
+ if (!ast_strlen_zero(ast_channel_accountcode(other->chan)) && ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan))) {
+ ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
+ ast_channel_accountcode(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+ ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
+ }
+ if (!ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan)) && ast_strlen_zero(ast_channel_accountcode(other->chan))) {
+ ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
+ ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+ ast_channel_accountcode_set(other->chan, ast_channel_peeraccount(bridge_channel->chan));
+ }
+ if (!ast_strlen_zero(ast_channel_peeraccount(other->chan)) && ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan))) {
+ ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
+ ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+ ast_channel_accountcode_set(bridge_channel->chan, ast_channel_peeraccount(other->chan));
+ }
+ if (bridge->num_channels == 2) {
+ if (strcmp(ast_channel_accountcode(bridge_channel->chan), ast_channel_peeraccount(other->chan))) {
+ ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
+ ast_channel_peeraccount(other->chan), ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+ ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
+ }
+ if (strcmp(ast_channel_accountcode(other->chan), ast_channel_peeraccount(bridge_channel->chan))) {
+ ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
+ ast_channel_peeraccount(bridge_channel->chan), ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+ ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
+ }
+ }
+ }
+}
+
+void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+ struct ast_bridge_channel *other = NULL;
+ const char *oldest_linkedid = ast_channel_linkedid(bridge_channel->chan);
+
+ AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+ if (other == swap) {
+ continue;
+ }
+ oldest_linkedid = ast_channel_oldest_linkedid(oldest_linkedid, ast_channel_linkedid(other->chan));
+ }
+
+ if (ast_strlen_zero(oldest_linkedid)) {
+ return;
+ }
+
+ ast_channel_linkedid_set(bridge_channel->chan, oldest_linkedid);
+ AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+ if (other == swap) {
+ continue;
+ }
+ ast_channel_linkedid_set(other->chan, oldest_linkedid);
+ }
+}
+
int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
{
struct ast_frame *dup;
@@ -529,6 +598,14 @@ static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
ast_bridge_channel_clear_roles(bridge_channel);
+ /* If we are not going to be hung up after leaving a bridge, and we were an
+ * outgoing channel, clear the outgoing flag.
+ */
+ if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING)
+ && (ast_channel_softhangup_internal_flag(bridge_channel->chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))) {
+ ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING);
+ }
+
bridge_dissolve_check(bridge_channel);
bridge->reconfigured = 1;
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
index 43862a013..09f2ca556 100644
--- a/main/bridging_basic.c
+++ b/main/bridging_basic.c
@@ -131,6 +131,9 @@ static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel
return -1;
}
+ ast_bridge_update_accountcodes(self, bridge_channel, swap);
+ ast_bridge_update_linkedids(self, bridge_channel, swap);
+
return ast_bridge_base_v_table.push(self, bridge_channel, swap);
}
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;
}
+
diff --git a/main/cel.c b/main/cel.c
index ff74ed1ef..8110b116e 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -433,16 +433,6 @@ static int dialstatus_cmp(void *obj, void *arg, int flags)
return !strcmp(blob1_id, blob2_id) ? CMP_MATCH | CMP_STOP : 0;
}
-/*!
- * \brief Map of ast_cel_ama_flags to strings
- */
-static const char * const cel_ama_flags[AST_CEL_AMA_FLAG_TOTAL] = {
- [AST_CEL_AMA_FLAG_NONE] = "NONE",
- [AST_CEL_AMA_FLAG_OMIT] = "OMIT",
- [AST_CEL_AMA_FLAG_BILLING] = "BILLING",
- [AST_CEL_AMA_FLAG_DOCUMENTATION] = "DOCUMENTATION",
-};
-
unsigned int ast_cel_check_enabled(void)
{
RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
@@ -625,16 +615,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type)
return S_OR(cel_event_types[type], "Unknown");
}
-const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag)
-{
- if (flag < 0 || flag >= ARRAY_LEN(cel_ama_flags)) {
- ast_log(LOG_WARNING, "Invalid AMA flag: %d\n", flag);
- return "Unknown";
- }
-
- return S_OR(cel_ama_flags[flag], "Unknown");
-}
-
static int cel_track_app(const char *const_app)
{
RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
diff --git a/main/channel.c b/main/channel.c
index 86a8f4994..89ab9ca36 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -104,7 +104,6 @@ struct ast_epoll_data {
/*! \brief Prevent new channel allocation if shutting down. */
static int shutting_down;
-static int uniqueint;
static int chancount;
unsigned long global_fin, global_fout;
@@ -116,6 +115,8 @@ AST_THREADSTORAGE(state2str_threadbuf);
* 100ms */
#define AST_DEFAULT_EMULATE_DTMF_DURATION 100
+#define DEFAULT_AMA_FLAGS AST_AMA_DOCUMENTATION
+
/*! Minimum amount of time between the end of the last digit and the beginning
* of a new one - 45ms */
#define AST_MIN_DTMF_GAP 45
@@ -984,7 +985,7 @@ static void ast_dummy_channel_destructor(void *obj);
static struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 0)))
__ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name,
const char *acctcode, const char *exten, const char *context,
- const char *linkedid, const int amaflag, const char *file, int line,
+ const char *linkedid, enum ama_flags amaflag, const char *file, int line,
const char *function, const char *name_fmt, va_list ap)
{
struct ast_channel *tmp;
@@ -1001,7 +1002,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
return NULL;
}
- if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor))) {
+ if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor, linkedid))) {
/* Channel structure allocation failure. */
return NULL;
}
@@ -1078,20 +1079,6 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
now = ast_tvnow();
ast_channel_creationtime_set(tmp, &now);
- if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
- ast_channel_uniqueid_build(tmp, "%li.%d", (long) time(NULL),
- ast_atomic_fetchadd_int(&uniqueint, 1));
- } else {
- ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME,
- (long) time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1));
- }
-
- if (!ast_strlen_zero(linkedid)) {
- ast_channel_linkedid_set(tmp, linkedid);
- } else {
- ast_channel_linkedid_set(tmp, ast_channel_uniqueid(tmp));
- }
-
ast_channel_internal_setup_topics(tmp);
if (!ast_strlen_zero(name_fmt)) {
@@ -1123,25 +1110,20 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
/* Reminder for the future: under what conditions do we NOT want to track cdrs on channels? */
/* These 4 variables need to be set up for the cdr_init() to work right */
- if (amaflag) {
+ if (amaflag != AST_AMA_NONE) {
ast_channel_amaflags_set(tmp, amaflag);
} else {
- ast_channel_amaflags_set(tmp, ast_default_amaflags);
+ ast_channel_amaflags_set(tmp, DEFAULT_AMA_FLAGS);
}
- if (!ast_strlen_zero(acctcode))
+ if (!ast_strlen_zero(acctcode)) {
ast_channel_accountcode_set(tmp, acctcode);
- else
- ast_channel_accountcode_set(tmp, ast_default_accountcode);
+ }
ast_channel_context_set(tmp, S_OR(context, "default"));
ast_channel_exten_set(tmp, S_OR(exten, "s"));
ast_channel_priority_set(tmp, 1);
- ast_channel_cdr_set(tmp, ast_cdr_alloc());
- ast_cdr_init(ast_channel_cdr(tmp), tmp);
- ast_cdr_start(ast_channel_cdr(tmp));
-
ast_atomic_fetchadd_int(&chancount, +1);
headp = ast_channel_varshead(tmp);
@@ -1165,7 +1147,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
* a lot of data into this func to do it here!
*/
if (ast_get_channel_tech(tech) || (tech2 && ast_get_channel_tech(tech2))) {
- ast_publish_channel_state(tmp);
+ ast_channel_publish_snapshot(tmp);
}
ast_channel_internal_finalize(tmp);
@@ -1176,7 +1158,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *cid_num,
const char *cid_name, const char *acctcode,
const char *exten, const char *context,
- const char *linkedid, const int amaflag,
+ const char *linkedid, const enum ama_flags amaflag,
const char *file, int line, const char *function,
const char *name_fmt, ...)
{
@@ -1202,7 +1184,7 @@ struct ast_channel *ast_dummy_channel_alloc(void)
struct ast_channel *tmp;
struct varshead *headp;
- if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor))) {
+ if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor, NULL))) {
/* Dummy channel structure allocation failure. */
return NULL;
}
@@ -2470,7 +2452,7 @@ static void ast_channel_destructor(void *obj)
ast_jb_destroy(chan);
if (ast_channel_cdr(chan)) {
- ast_cdr_discard(ast_channel_cdr(chan));
+ ast_cdr_free(ast_channel_cdr(chan));
ast_channel_cdr_set(chan, NULL);
}
@@ -2530,7 +2512,7 @@ static void ast_dummy_channel_destructor(void *obj)
ast_var_delete(vardata);
if (ast_channel_cdr(chan)) {
- ast_cdr_discard(ast_channel_cdr(chan));
+ ast_cdr_free(ast_channel_cdr(chan));
ast_channel_cdr_set(chan, NULL);
}
@@ -2884,26 +2866,17 @@ int ast_hangup(struct ast_channel *chan)
ast_cc_offer(chan);
- ast_publish_channel_state(chan);
-
- if (ast_channel_cdr(chan) && !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_BRIDGED) &&
- !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED) &&
- (ast_channel_cdr(chan)->disposition != AST_CDR_NULL || ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED))) {
- ast_channel_lock(chan);
- ast_cdr_end(ast_channel_cdr(chan));
- ast_cdr_detach(ast_channel_cdr(chan));
- ast_channel_cdr_set(chan, NULL);
- ast_channel_unlock(chan);
- }
+ ast_channel_publish_snapshot(chan);
ast_channel_unref(chan);
return 0;
}
-int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
+int ast_raw_answer(struct ast_channel *chan)
{
int res = 0;
+ struct timeval answertime;
ast_channel_lock(chan);
@@ -2919,6 +2892,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
return -1;
}
+ answertime = ast_tvnow();
+ ast_channel_answertime_set(chan, &answertime);
+
ast_channel_unlock(chan);
switch (ast_channel_state(chan)) {
@@ -2929,18 +2905,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
res = ast_channel_tech(chan)->answer(chan);
}
ast_setstate(chan, AST_STATE_UP);
- if (cdr_answer) {
- ast_cdr_answer(ast_channel_cdr(chan));
- }
ast_channel_unlock(chan);
break;
case AST_STATE_UP:
- /* Calling ast_cdr_answer when it it has previously been called
- * is essentially a no-op, so it is safe.
- */
- if (cdr_answer) {
- ast_cdr_answer(ast_channel_cdr(chan));
- }
break;
default:
break;
@@ -2951,13 +2918,13 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
return res;
}
-int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
+int __ast_answer(struct ast_channel *chan, unsigned int delay)
{
int res = 0;
enum ast_channel_state old_state;
old_state = ast_channel_state(chan);
- if ((res = ast_raw_answer(chan, cdr_answer))) {
+ if ((res = ast_raw_answer(chan))) {
return res;
}
@@ -3059,7 +3026,27 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
int ast_answer(struct ast_channel *chan)
{
- return __ast_answer(chan, 0, 1);
+ return __ast_answer(chan, 0);
+}
+
+int ast_channel_get_duration(struct ast_channel *chan)
+{
+ ast_assert(NULL != chan);
+
+ if (ast_tvzero(ast_channel_creationtime(chan))) {
+ return 0;
+ }
+ return (ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(chan)) / 1000);
+}
+
+int ast_channel_get_up_time(struct ast_channel *chan)
+{
+ ast_assert(NULL != chan);
+
+ if (ast_tvzero(ast_channel_answertime(chan))) {
+ return 0;
+ }
+ return (ast_tvdiff_ms(ast_tvnow(), ast_channel_answertime(chan)) / 1000);
}
void ast_deactivate_generator(struct ast_channel *chan)
@@ -4472,6 +4459,33 @@ void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast
}
}
+enum ama_flags ast_channel_string2amaflag(const char *flag)
+{
+ if (!strcasecmp(flag, "default"))
+ return DEFAULT_AMA_FLAGS;
+ if (!strcasecmp(flag, "omit"))
+ return AST_AMA_OMIT;
+ if (!strcasecmp(flag, "billing"))
+ return AST_AMA_BILLING;
+ if (!strcasecmp(flag, "documentation"))
+ return AST_AMA_DOCUMENTATION;
+ return AST_AMA_NONE;
+}
+
+const char *ast_channel_amaflags2string(enum ama_flags flag)
+{
+ switch (flag) {
+ case AST_AMA_OMIT:
+ return "OMIT";
+ case AST_AMA_BILLING:
+ return "BILLING";
+ case AST_AMA_DOCUMENTATION:
+ return "DOCUMENTATION";
+ default:
+ return "Unknown";
+ }
+}
+
int ast_indicate_data(struct ast_channel *chan, int _condition,
const void *data, size_t datalen)
{
@@ -5625,15 +5639,15 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan
}
if (oh->account) {
ast_channel_lock(new_chan);
- ast_cdr_setaccount(new_chan, oh->account);
+ ast_channel_accountcode_set(new_chan, oh->account);
ast_channel_unlock(new_chan);
}
} else if (caller) { /* no outgoing helper so use caller if avaliable */
call_forward_inherit(new_chan, caller, orig);
}
+ ast_set_flag(ast_channel_flags(new_chan), AST_FLAG_ORIGINATED);
ast_channel_lock_both(orig, new_chan);
- ast_copy_flags(ast_channel_cdr(new_chan), ast_channel_cdr(orig), AST_CDR_FLAG_ORIGINATED);
ast_channel_accountcode_set(new_chan, ast_channel_accountcode(orig));
ast_party_connected_line_copy(ast_channel_connected(new_chan), ast_channel_connected(orig));
ast_party_redirecting_copy(ast_channel_redirecting(new_chan), ast_channel_redirecting(orig));
@@ -5699,7 +5713,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
}
if (oh->account) {
ast_channel_lock(chan);
- ast_cdr_setaccount(chan, oh->account);
+ ast_channel_accountcode_set(chan, oh->account);
ast_channel_unlock(chan);
}
}
@@ -5714,7 +5728,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
*/
ast_set_callerid(chan, cid_num, cid_name, cid_num);
- ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_ORIGINATED);
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
ast_party_connected_line_set_init(&connected, ast_channel_connected(chan));
if (cid_num) {
connected.id.number.valid = 1;
@@ -5764,25 +5778,21 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
break;
case AST_CONTROL_BUSY:
- ast_cdr_busy(ast_channel_cdr(chan));
*outstate = f->subclass.integer;
timeout = 0;
break;
case AST_CONTROL_INCOMPLETE:
- ast_cdr_failed(ast_channel_cdr(chan));
*outstate = AST_CONTROL_CONGESTION;
timeout = 0;
break;
case AST_CONTROL_CONGESTION:
- ast_cdr_failed(ast_channel_cdr(chan));
*outstate = f->subclass.integer;
timeout = 0;
break;
case AST_CONTROL_ANSWER:
- ast_cdr_answer(ast_channel_cdr(chan));
*outstate = f->subclass.integer;
timeout = 0; /* trick to force exit from the while() */
break;
@@ -5833,28 +5843,10 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
*outstate = AST_CONTROL_ANSWER;
if (res <= 0) {
- struct ast_cdr *chancdr;
ast_channel_lock(chan);
if (AST_CONTROL_RINGING == last_subclass) {
ast_channel_hangupcause_set(chan, AST_CAUSE_NO_ANSWER);
}
- if (!ast_channel_cdr(chan) && (chancdr = ast_cdr_alloc())) {
- ast_channel_cdr_set(chan, chancdr);
- ast_cdr_init(ast_channel_cdr(chan), chan);
- }
- if (ast_channel_cdr(chan)) {
- char tmp[256];
-
- snprintf(tmp, sizeof(tmp), "%s/%s", type, addr);
- ast_cdr_setapp(ast_channel_cdr(chan), "Dial", tmp);
- ast_cdr_update(chan);
- ast_cdr_start(ast_channel_cdr(chan));
- ast_cdr_end(ast_channel_cdr(chan));
- /* If the cause wasn't handled properly */
- if (ast_cdr_disposition(ast_channel_cdr(chan), ast_channel_hangupcause(chan))) {
- ast_cdr_failed(ast_channel_cdr(chan));
- }
- }
ast_channel_unlock(chan);
ast_hangup(chan);
chan = NULL;
@@ -6024,9 +6016,6 @@ int ast_call(struct ast_channel *chan, const char *addr, int timeout)
/* Stop if we're a zombie or need a soft hangup */
ast_channel_lock(chan);
if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan)) {
- if (ast_channel_cdr(chan)) {
- ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED);
- }
if (ast_channel_tech(chan)->call)
res = ast_channel_tech(chan)->call(chan, addr, timeout);
ast_set_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
@@ -6517,32 +6506,20 @@ static void clone_variables(struct ast_channel *original, struct ast_channel *cl
}
}
-
-
-/* return the oldest of two linkedids. linkedid is derived from
- uniqueid which is formed like this: [systemname-]ctime.seq
-
- The systemname, and the dash are optional, followed by the epoch
- time followed by an integer sequence. Note that this is not a
- decimal number, since 1.2 is less than 1.11 in uniqueid land.
-
- To compare two uniqueids, we parse out the integer values of the
- time and the sequence numbers and compare them, with time trumping
- sequence.
-*/
-static const char *oldest_linkedid(const char *a, const char *b)
+const char *ast_channel_oldest_linkedid(const char *a, const char *b)
{
const char *satime, *saseq;
const char *sbtime, *sbseq;
const char *dash;
-
unsigned int atime, aseq, btime, bseq;
- if (ast_strlen_zero(a))
+ if (ast_strlen_zero(a)) {
return b;
+ }
- if (ast_strlen_zero(b))
+ if (ast_strlen_zero(b)) {
return a;
+ }
satime = a;
sbtime = b;
@@ -6558,8 +6535,9 @@ static const char *oldest_linkedid(const char *a, const char *b)
/* the sequence comes after the '.' */
saseq = strchr(satime, '.');
sbseq = strchr(sbtime, '.');
- if (!saseq || !sbseq)
+ if (!saseq || !sbseq) {
return NULL;
+ }
saseq++;
sbseq++;
@@ -6578,122 +6556,6 @@ static const char *oldest_linkedid(const char *a, const char *b)
}
}
-/*! Set the channel's linkedid to the given string, and also check to
- * see if the channel's old linkedid is now being retired */
-static void ast_channel_change_linkedid(struct ast_channel *chan, const char *linkedid)
-{
- ast_assert(linkedid != NULL);
- /* if the linkedid for this channel is being changed from something, check... */
- if (ast_channel_linkedid(chan) && !strcmp(ast_channel_linkedid(chan), linkedid)) {
- return;
- }
-
- ast_channel_linkedid_set(chan, linkedid);
- ast_cel_linkedid_ref(linkedid);
-}
-
-/*! \brief Propagate the oldest linkedid between associated channels */
-void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
-{
- const char* linkedid=NULL;
- struct ast_channel *bridged;
-
-/*
- * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel().
- * BUGBUG this needs to be updated to not use ast_bridged_channel().
- *
- * We may be better off making this a function of the bridging
- * framework. Essentially, as each channel joins a bridge, the
- * oldest linkedid should be propagated between all pairs of
- * channels. This should be handled by bridging (unless you're
- * in an infinite wait bridge...) just like the BRIDGEPEER
- * channel variable.
- *
- * This is currently called in two places:
- *
- * (1) In channel masquerade. To some extent this shouldn't
- * really be done any longer - we don't really want a channel to
- * have its linkedid change, even if it replaces a channel that
- * had an older linkedid. The two channels aren't really
- * 'related', they're simply swapping with each other.
- *
- * (2) In features.c as two channels are bridged.
- */
- linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer));
- linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan));
- linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer));
- if (ast_channel_internal_bridged_channel(chan)) {
- bridged = ast_bridged_channel(chan);
- if (bridged && bridged != peer) {
- linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged));
- linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged));
- }
- }
- if (ast_channel_internal_bridged_channel(peer)) {
- bridged = ast_bridged_channel(peer);
- if (bridged && bridged != chan) {
- linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged));
- linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged));
- }
- }
-
- /* just in case setting a stringfield to itself causes problems */
- linkedid = ast_strdupa(linkedid);
-
- ast_channel_change_linkedid(chan, linkedid);
- ast_channel_change_linkedid(peer, linkedid);
- if (ast_channel_internal_bridged_channel(chan)) {
- bridged = ast_bridged_channel(chan);
- if (bridged && bridged != peer) {
- ast_channel_change_linkedid(bridged, linkedid);
- }
- }
- if (ast_channel_internal_bridged_channel(peer)) {
- bridged = ast_bridged_channel(peer);
- if (bridged && bridged != chan) {
- ast_channel_change_linkedid(bridged, linkedid);
- }
- }
-}
-
-#if 0 //BUGBUG setting up peeraccount needs to be removed.
-/* copy accountcode and peeraccount across during a link */
-static void ast_set_owners_and_peers(struct ast_channel *chan1,
- struct ast_channel *chan2)
-{
- if (!ast_strlen_zero(ast_channel_accountcode(chan1)) && ast_strlen_zero(ast_channel_peeraccount(chan2))) {
- ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n",
- ast_channel_accountcode(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
- ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1));
- }
- if (!ast_strlen_zero(ast_channel_accountcode(chan2)) && ast_strlen_zero(ast_channel_peeraccount(chan1))) {
- ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n",
- ast_channel_accountcode(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
- ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2));
- }
- if (!ast_strlen_zero(ast_channel_peeraccount(chan1)) && ast_strlen_zero(ast_channel_accountcode(chan2))) {
- ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n",
- ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
- ast_channel_accountcode_set(chan2, ast_channel_peeraccount(chan1));
- }
- if (!ast_strlen_zero(ast_channel_peeraccount(chan2)) && ast_strlen_zero(ast_channel_accountcode(chan1))) {
- ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n",
- ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
- ast_channel_accountcode_set(chan1, ast_channel_peeraccount(chan2));
- }
- if (0 != strcmp(ast_channel_accountcode(chan1), ast_channel_peeraccount(chan2))) {
- ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n",
- ast_channel_peeraccount(chan2), ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
- ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1));
- }
- if (0 != strcmp(ast_channel_accountcode(chan2), ast_channel_peeraccount(chan1))) {
- ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n",
- ast_channel_peeraccount(chan1), ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
- ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2));
- }
-}
-#endif //BUGBUG
-
/*!
* \internal
* \brief Transfer COLP between target and transferee channels.
@@ -6775,7 +6637,6 @@ void ast_do_masquerade(struct ast_channel *original)
} exchange;
struct ast_channel *clonechan, *chans[2];
struct ast_channel *bridged;
- struct ast_cdr *cdr;
struct ast_datastore *xfer_ds;
struct xfer_masquerade_ds *xfer_colp;
struct ast_format rformat;
@@ -6943,9 +6804,6 @@ void ast_do_masquerade(struct ast_channel *original)
snprintf(tmp_name, sizeof(tmp_name), "%s<ZOMBIE>", ast_channel_name(clonechan)); /* quick, hide the brains! */
__ast_change_name_nolink(clonechan, tmp_name);
- /* share linked id's */
- ast_channel_set_linkgroup(original, clonechan);
-
/* Swap the technologies */
t = ast_channel_tech(original);
ast_channel_tech_set(original, ast_channel_tech(clonechan));
@@ -6955,11 +6813,6 @@ void ast_do_masquerade(struct ast_channel *original)
ast_channel_tech_pvt_set(original, ast_channel_tech_pvt(clonechan));
ast_channel_tech_pvt_set(clonechan, t_pvt);
- /* Swap the cdrs */
- cdr = ast_channel_cdr(original);
- ast_channel_cdr_set(original, ast_channel_cdr(clonechan));
- ast_channel_cdr_set(clonechan, cdr);
-
/* Swap the alertpipes */
ast_channel_internal_alertpipe_swap(original, clonechan);
@@ -7098,7 +6951,7 @@ void ast_do_masquerade(struct ast_channel *original)
ast_channel_redirecting_set(original, ast_channel_redirecting(clonechan));
ast_channel_redirecting_set(clonechan, &exchange.redirecting);
- ast_publish_channel_state(original);
+ ast_channel_publish_snapshot(original);
/* Restore original timing file descriptor */
ast_channel_set_fd(original, AST_TIMING_FD, ast_channel_timingfd(original));
@@ -7257,11 +7110,8 @@ void ast_set_callerid(struct ast_channel *chan, const char *cid_num, const char
ast_free(ast_channel_caller(chan)->ani.number.str);
ast_channel_caller(chan)->ani.number.str = ast_strdup(cid_ani);
}
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
- ast_publish_channel_state(chan);
+ ast_channel_publish_snapshot(chan);
ast_channel_unlock(chan);
}
@@ -7287,10 +7137,7 @@ void ast_channel_set_caller_event(struct ast_channel *chan, const struct ast_par
ast_channel_lock(chan);
ast_party_caller_set(ast_channel_caller(chan), caller, update);
- ast_publish_channel_state(chan);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
+ ast_channel_publish_snapshot(chan);
ast_channel_unlock(chan);
}
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 1e21cced0..afa4c9f3d 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -38,6 +38,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <unistd.h>
#include <fcntl.h>
+#include "asterisk/paths.h"
#include "asterisk/channel.h"
#include "asterisk/channel_internal.h"
#include "asterisk/data.h"
@@ -143,6 +144,7 @@ struct ast_channel {
struct ast_namedgroups *named_callgroups; /*!< Named call group for call pickups */
struct ast_namedgroups *named_pickupgroups; /*!< Named pickup group - which call groups can be picked up? */
struct timeval creationtime; /*!< The time of channel creation */
+ struct timeval answertime; /*!< The time the channel was answered */
struct ast_readq_list readq;
struct ast_jb jb; /*!< The jitterbuffer state */
struct timeval dtmf_tv; /*!< The time that an in process digit began, or the last digit ended */
@@ -206,6 +208,9 @@ struct ast_channel {
struct stasis_subscription *endpoint_forward; /*!< Subscription for event forwarding to endpoint's topic */
};
+/*! \brief The monotonically increasing integer counter for channel uniqueids */
+static int uniqueint;
+
/* AST_DATA definitions, which will probably have to be re-thought since the channel will be opaque */
#if 0 /* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */
@@ -333,7 +338,7 @@ int ast_channel_data_add_structure(struct ast_data *tree,
if (!enum_node) {
return -1;
}
- ast_data_add_str(enum_node, "text", ast_cdr_flags2str(ast_channel_amaflags(chan)));
+ ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(ast_channel_amaflags(chan)));
ast_data_add_int(enum_node, "value", ast_channel_amaflags(chan));
/* transfercapability */
@@ -400,8 +405,6 @@ int ast_channel_data_add_structure(struct ast_data *tree,
return -1;
}
- ast_cdr_data_add_structure(data_cdr, ast_channel_cdr(chan), 1);
-
return 0;
}
@@ -413,9 +416,11 @@ int ast_channel_data_cmp_structure(const struct ast_data_search *tree,
/* ACCESSORS */
-#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish) \
+#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish, assert_on_null) \
void ast_channel_##field##_set(struct ast_channel *chan, const char *value) \
{ \
+ if ((assert_on_null)) ast_assert(!ast_strlen_zero(value)); \
+ if (!strcmp(value, chan->field)) return; \
ast_string_field_set(chan, field, value); \
if (publish) ast_channel_publish_snapshot(chan); \
} \
@@ -431,20 +436,20 @@ void ast_channel_##field##_build(struct ast_channel *chan, const char *fmt, ...)
va_start(ap, fmt); \
ast_channel_##field##_build_va(chan, fmt, ap); \
va_end(ap); \
- if (publish) ast_channel_publish_snapshot(chan); \
}
-DEFINE_STRINGFIELD_SETTERS_FOR(name, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(language, 1);
-DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(name, 0, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(language, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(linkedid, 1, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0, 0);
#define DEFINE_STRINGFIELD_GETTER_FOR(field) const char *ast_channel_##field(const struct ast_channel *chan) \
{ \
@@ -464,12 +469,6 @@ DEFINE_STRINGFIELD_GETTER_FOR(parkinglot);
DEFINE_STRINGFIELD_GETTER_FOR(hangupsource);
DEFINE_STRINGFIELD_GETTER_FOR(dialcontext);
-void ast_channel_linkedid_set(struct ast_channel *chan, const char *value)
-{
- ast_assert(!ast_strlen_zero(value));
- ast_string_field_set(chan, linkedid, value);
-}
-
const char *ast_channel_appl(const struct ast_channel *chan)
{
return chan->appl;
@@ -555,14 +554,20 @@ void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval va
chan->sending_dtmf_tv = value;
}
-int ast_channel_amaflags(const struct ast_channel *chan)
+enum ama_flags ast_channel_amaflags(const struct ast_channel *chan)
{
return chan->amaflags;
}
-void ast_channel_amaflags_set(struct ast_channel *chan, int value)
+
+void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value)
{
+ if (chan->amaflags == value) {
+ return;
+ }
chan->amaflags = value;
+ ast_channel_publish_snapshot(chan);
}
+
#ifdef HAVE_EPOLL
int ast_channel_epfd(const struct ast_channel *chan)
{
@@ -1043,6 +1048,16 @@ void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *valu
chan->creationtime = *value;
}
+struct timeval ast_channel_answertime(struct ast_channel *chan)
+{
+ return chan->answertime;
+}
+
+void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value)
+{
+ chan->answertime = *value;
+}
+
/* Evil softhangup accessors */
int ast_channel_softhangup_internal_flag(struct ast_channel *chan)
{
@@ -1350,7 +1365,7 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags)
#define DIALED_CAUSES_BUCKETS 37
-struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function)
+struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function)
{
struct ast_channel *tmp;
#if defined(REF_DEBUG)
@@ -1368,7 +1383,21 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj),
}
if (!(tmp->dialed_causes = ao2_container_alloc(DIALED_CAUSES_BUCKETS, pvt_cause_hash_fn, pvt_cause_cmp_fn))) {
- return ast_channel_unref(tmp);
+ return ast_channel_unref(tmp);
+ }
+
+ if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
+ ast_channel_uniqueid_build(tmp, "%li.%d", (long)time(NULL),
+ ast_atomic_fetchadd_int(&uniqueint, 1));
+ } else {
+ ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME,
+ (long)time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1));
+ }
+
+ if (!ast_strlen_zero(linkedid)) {
+ ast_string_field_set(tmp, linkedid, linkedid);
+ } else {
+ ast_string_field_set(tmp, linkedid, tmp->uniqueid);
}
return tmp;
diff --git a/main/cli.c b/main/cli.c
index 683ae9c3e..3431d1de8 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -932,8 +932,8 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
bridge = ast_channel_get_bridge(c);
if (!count) {
- if ((concise || verbose) && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
- int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
+ if ((concise || verbose) && !ast_tvzero(ast_channel_creationtime(c))) {
+ int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000);
if (verbose) {
int durh = duration / 3600;
int durm = (duration % 3600) / 60;
@@ -1465,8 +1465,8 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_channel_lock(c);
- if (ast_channel_cdr(c)) {
- elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
+ if (!ast_tvzero(ast_channel_creationtime(c))) {
+ elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec;
hour = elapsed_seconds / 3600;
min = (elapsed_seconds % 3600) / 60;
sec = elapsed_seconds % 60;
@@ -1565,7 +1565,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_str_append(&output, 0, " Variables:\n%s\n", ast_str_buffer(obuf));
}
- if (ast_channel_cdr(c) && ast_cdr_serialize_variables(ast_channel_cdr(c), &obuf, '=', '\n', 1)) {
+ if (ast_cdr_serialize_variables(ast_channel_name(c), &obuf, '=', '\n')) {
ast_str_append(&output, 0, " CDR Variables:\n%s\n", ast_str_buffer(obuf));
}
diff --git a/main/dial.c b/main/dial.c
index 840f681d6..ab35373c5 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -332,7 +332,7 @@ int ast_dial_prerun(struct ast_dial *dial, struct ast_channel *chan, struct ast_
}
/*! \brief Helper function that does the beginning dialing per-appended channel */
-static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan)
+static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan, int async)
{
char numsubst[AST_MAX_EXTENSION];
int res = 1;
@@ -351,9 +351,10 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann
ast_hangup(channel->owner);
channel->owner = NULL;
} else {
- if (chan)
+ if (chan) {
ast_poll_channel_add(chan, channel->owner);
- ast_channel_publish_dial(chan, channel->owner, channel->device, NULL);
+ }
+ ast_channel_publish_dial(async ? NULL : chan, channel->owner, channel->device, NULL);
res = 1;
ast_verb(3, "Called %s\n", numsubst);
}
@@ -362,7 +363,7 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann
}
/*! \brief Helper function that does the beginning dialing per dial structure */
-static int begin_dial(struct ast_dial *dial, struct ast_channel *chan)
+static int begin_dial(struct ast_dial *dial, struct ast_channel *chan, int async)
{
struct ast_dial_channel *channel = NULL;
int success = 0;
@@ -370,7 +371,7 @@ static int begin_dial(struct ast_dial *dial, struct ast_channel *chan)
/* Iterate through channel list, requesting and calling each one */
AST_LIST_LOCK(&dial->channels);
AST_LIST_TRAVERSE(&dial->channels, channel, list) {
- success += begin_dial_channel(channel, chan);
+ success += begin_dial_channel(channel, chan, async);
}
AST_LIST_UNLOCK(&dial->channels);
@@ -409,7 +410,7 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c
AST_LIST_UNLOCK(&dial->channels);
/* Finally give it a go... send it out into the world */
- begin_dial_channel(channel, chan);
+ begin_dial_channel(channel, chan, chan ? 0 : 1);
/* Drop the original channel */
ast_hangup(original);
@@ -819,7 +820,7 @@ enum ast_dial_result ast_dial_run(struct ast_dial *dial, struct ast_channel *cha
}
/* Dial each requested channel */
- if (!begin_dial(dial, chan))
+ if (!begin_dial(dial, chan, async))
return AST_DIAL_RESULT_FAILED;
/* If we are running async spawn a thread and send it away... otherwise block here */
diff --git a/main/features.c b/main/features.c
index d5b5ce295..c44520c53 100644
--- a/main/features.c
+++ b/main/features.c
@@ -3618,7 +3618,7 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
/* Answer if need be */
if (ast_channel_state(chan) != AST_STATE_UP) {
- if (ast_raw_answer(chan, 1)) {
+ if (ast_raw_answer(chan)) {
return -1;
}
}
@@ -3628,8 +3628,6 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
ast_channel_log("Pre-bridge CHAN Channel info", chan);
ast_channel_log("Pre-bridge PEER Channel info", peer);
#endif
- /* two channels are being marked as linked here */
- ast_channel_set_linkgroup(chan, peer);
/*
* If we are bridging a call, stop worrying about forwarding
diff --git a/main/manager.c b/main/manager.c
index 0e402aeb8..d4960d6de 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -3770,8 +3770,8 @@ static int action_status(struct mansession *s, const struct message *m)
bridge_text[0] = '\0';
}
if (ast_channel_pbx(c)) {
- if (ast_channel_cdr(c)) {
- elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
+ if (!ast_tvzero(ast_channel_creationtime(c))) {
+ elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec;
}
astman_append(s,
"Event: Status\r\n"
@@ -5120,7 +5120,7 @@ static int action_coresettings(struct mansession *s, const struct message *m)
ast_config_AST_RUN_GROUP,
option_maxfiles,
AST_CLI_YESNO(ast_realtime_enabled()),
- AST_CLI_YESNO(check_cdr_enabled()),
+ AST_CLI_YESNO(ast_cdr_is_enabled()),
AST_CLI_YESNO(check_webmanager_enabled())
);
return 0;
@@ -5228,8 +5228,8 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
ast_channel_lock(c);
bc = ast_bridged_channel(c);
- if (ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
- duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
+ if (!ast_tvzero(ast_channel_creationtime(c))) {
+ duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000);
durh = duration / 3600;
durm = (duration % 3600) / 60;
durs = duration % 60;
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 277dc873d..5ae35b21d 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -153,6 +153,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</syntax>
</managerEventInstance>
</managerEvent>
+ <managerEvent language="en_US" name="NewAccountCode">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a Channel's AccountCode is changed.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ <parameter name="OldAccountCode">
+ <para>The channel's previous account code</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
<managerEvent language="en_US" name="DialBegin">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a dial action has started.</synopsis>
@@ -627,7 +638,8 @@ static struct ast_manager_event_blob *channel_newexten(
return NULL;
}
- if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) {
+ if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)
+ && !strcmp(old_snapshot->appl, new_snapshot->appl)) {
return NULL;
}
@@ -662,10 +674,28 @@ static struct ast_manager_event_blob *channel_new_callerid(
ast_describe_caller_presentation(new_snapshot->caller_pres));
}
+static struct ast_manager_event_blob *channel_new_accountcode(
+ struct ast_channel_snapshot *old_snapshot,
+ struct ast_channel_snapshot *new_snapshot)
+{
+ if (!old_snapshot || !new_snapshot) {
+ return NULL;
+ }
+
+ if (!strcmp(old_snapshot->accountcode, new_snapshot->accountcode)) {
+ return NULL;
+ }
+
+ return ast_manager_event_blob_create(
+ EVENT_FLAG_CALL, "NewAccountCode",
+ "OldAccountCode: %s\r\n", old_snapshot->accountcode);
+}
+
channel_snapshot_monitor channel_monitors[] = {
channel_state_change,
channel_newexten,
- channel_new_callerid
+ channel_new_callerid,
+ channel_new_accountcode
};
static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
diff --git a/main/pbx.c b/main/pbx.c
index 2e8c32169..af988dcf9 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -470,36 +470,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<ref type="function">Exception</ref>
</see-also>
</application>
- <application name="ResetCDR" language="en_US">
- <synopsis>
- Resets the Call Data Record.
- </synopsis>
- <syntax>
- <parameter name="options">
- <optionlist>
- <option name="w">
- <para>Store the current CDR record before resetting it.</para>
- </option>
- <option name="a">
- <para>Store any stacked records.</para>
- </option>
- <option name="v">
- <para>Save CDR variables.</para>
- </option>
- <option name="e">
- <para>Enable CDR only (negate effects of NoCDR).</para>
- </option>
- </optionlist>
- </parameter>
- </syntax>
- <description>
- <para>This application causes the Call Data Record to be reset.</para>
- </description>
- <see-also>
- <ref type="application">ForkCDR</ref>
- <ref type="application">NoCDR</ref>
- </see-also>
- </application>
<application name="Ringing" language="en_US">
<synopsis>
Indicate ringing tone.
@@ -657,9 +627,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</syntax>
<description>
<para>This application will set the channel's AMA Flags for billing purposes.</para>
+ <warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning>
</description>
<see-also>
<ref type="function">CDR</ref>
+ <ref type="function">CHANNEL</ref>
</see-also>
</application>
<application name="Wait" language="en_US">
@@ -1139,7 +1111,6 @@ static int pbx_builtin_background(struct ast_channel *, const char *);
static int pbx_builtin_wait(struct ast_channel *, const char *);
static int pbx_builtin_waitexten(struct ast_channel *, const char *);
static int pbx_builtin_incomplete(struct ast_channel *, const char *);
-static int pbx_builtin_resetcdr(struct ast_channel *, const char *);
static int pbx_builtin_setamaflags(struct ast_channel *, const char *);
static int pbx_builtin_ringing(struct ast_channel *, const char *);
static int pbx_builtin_proceeding(struct ast_channel *, const char *);
@@ -1326,7 +1297,6 @@ static struct pbx_builtin {
{ "Proceeding", pbx_builtin_proceeding },
{ "Progress", pbx_builtin_progress },
{ "RaiseException", pbx_builtin_raise_exception },
- { "ResetCDR", pbx_builtin_resetcdr },
{ "Ringing", pbx_builtin_ringing },
{ "SayAlpha", pbx_builtin_saycharacters },
{ "SayDigits", pbx_builtin_saydigits },
@@ -1565,15 +1535,13 @@ int pbx_exec(struct ast_channel *c, /*!< Channel */
const char *saved_c_appl;
const char *saved_c_data;
- if (ast_channel_cdr(c) && !ast_check_hangup(c))
- ast_cdr_setapp(ast_channel_cdr(c), app->name, data);
-
/* save channel values */
saved_c_appl= ast_channel_appl(c);
saved_c_data= ast_channel_data(c);
ast_channel_appl_set(c, app->name);
ast_channel_data_set(c, data);
+ ast_channel_publish_snapshot(c);
if (app->module)
u = __ast_module_user_add(app->module, c);
@@ -5713,10 +5681,6 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
ast_channel_lock(chan);
- if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
- ast_cdr_end(ast_channel_cdr(chan));
- }
-
/* Set h exten location */
if (context != ast_channel_context(chan)) {
ast_channel_context_set(chan, context);
@@ -5797,10 +5761,6 @@ int ast_pbx_hangup_handler_run(struct ast_channel *chan)
return 0;
}
- if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
- ast_cdr_end(ast_channel_cdr(chan));
- }
-
/*
* Make sure that the channel is marked as hungup since we are
* going to run the hangup handlers on it.
@@ -6114,12 +6074,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
set_ext_pri(c, "s", 1);
}
- ast_channel_lock(c);
- if (ast_channel_cdr(c)) {
- /* allow CDR variables that have been collected after channel was created to be visible during call */
- ast_cdr_update(c);
- }
- ast_channel_unlock(c);
for (;;) {
char dst_exten[256]; /* buffer to accumulate digits */
int pos = 0; /* XXX should check bounds */
@@ -6229,11 +6183,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
}
/* Call timed out with no special extension to jump to. */
}
- ast_channel_lock(c);
- if (ast_channel_cdr(c)) {
- ast_cdr_update(c);
- }
- ast_channel_unlock(c);
error = 1;
break;
}
@@ -6339,12 +6288,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
}
}
}
- ast_channel_lock(c);
- if (ast_channel_cdr(c)) {
- ast_verb(2, "CDR updated on %s\n",ast_channel_name(c));
- ast_cdr_update(c);
- }
- ast_channel_unlock(c);
}
}
@@ -9991,13 +9934,16 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
}
dialed = ast_dial_get_channel(outgoing->dial, 0);
+ if (!dialed) {
+ return -1;
+ }
ast_set_variables(dialed, vars);
if (account) {
- ast_cdr_setaccount(dialed, account);
+ ast_channel_accountcode_set(dialed, account);
}
- ast_set_flag(ast_channel_cdr(dialed), AST_CDR_FLAG_ORIGINATED);
+ ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
if (!ast_strlen_zero(cid_num) && !ast_strlen_zero(cid_name)) {
struct ast_party_connected_line connected;
@@ -10043,12 +9989,13 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
/* Wait for dialing to complete */
if (channel || synchronous) {
if (channel) {
+ ast_channel_ref(*channel);
ast_channel_unlock(*channel);
}
while (!outgoing->dialed) {
ast_cond_wait(&outgoing->cond, &outgoing->lock);
}
- if (channel) {
+ if (channel && *channel) {
ast_channel_lock(*channel);
}
}
@@ -10078,7 +10025,7 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
}
if (account) {
- ast_cdr_setaccount(failed, account);
+ ast_channel_accountcode_set(failed, account);
}
set_ext_pri(failed, "failed", 1);
@@ -10387,8 +10334,8 @@ static int pbx_builtin_busy(struct ast_channel *chan, const char *data)
/* Don't change state of an UP channel, just indicate
busy in audio */
if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY);
ast_setstate(chan, AST_STATE_BUSY);
- ast_cdr_busy(ast_channel_cdr(chan));
}
wait_for_hangup(chan, data);
return -1;
@@ -10403,8 +10350,8 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
/* Don't change state of an UP channel, just indicate
congestion in audio */
if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION);
ast_setstate(chan, AST_STATE_BUSY);
- ast_cdr_congestion(ast_channel_cdr(chan));
}
wait_for_hangup(chan, data);
return -1;
@@ -10416,7 +10363,6 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
{
int delay = 0;
- int answer_cdr = 1;
char *parse;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(delay);
@@ -10424,7 +10370,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
);
if (ast_strlen_zero(data)) {
- return __ast_answer(chan, 0, 1);
+ return __ast_answer(chan, 0);
}
parse = ast_strdupa(data);
@@ -10439,10 +10385,12 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
}
if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
- answer_cdr = 0;
+ if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+ ast_log(AST_LOG_WARNING, "Failed to disable CDR on %s\n", ast_channel_name(chan));
+ }
}
- return __ast_answer(chan, delay, answer_cdr);
+ return __ast_answer(chan, delay);
}
static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
@@ -10459,7 +10407,7 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
if (ast_check_hangup(chan)) {
return -1;
} else if (ast_channel_state(chan) != AST_STATE_UP && answer) {
- __ast_answer(chan, 0, 1);
+ __ast_answer(chan, 0);
}
ast_indicate(chan, AST_CONTROL_INCOMPLETE);
@@ -10467,39 +10415,30 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
return AST_PBX_INCOMPLETE;
}
-AST_APP_OPTIONS(resetcdr_opts, {
- AST_APP_OPTION('w', AST_CDR_FLAG_POSTED),
- AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED),
- AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
- AST_APP_OPTION('e', AST_CDR_FLAG_POST_ENABLE),
-});
-
/*!
* \ingroup applications
*/
-static int pbx_builtin_resetcdr(struct ast_channel *chan, const char *data)
+static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
{
- char *args;
- struct ast_flags flags = { 0 };
+ ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n");
- if (!ast_strlen_zero(data)) {
- args = ast_strdupa(data);
- ast_app_parse_options(resetcdr_opts, &flags, NULL, args);
+ if (ast_strlen_zero(data)) {
+ ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n");
+ return 0;
}
-
- ast_cdr_reset(ast_channel_cdr(chan), &flags);
-
- return 0;
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
-{
/* Copy the AMA Flags as specified */
ast_channel_lock(chan);
- ast_cdr_setamaflags(chan, data ? data : "");
+ if (isdigit(data[0])) {
+ int amaflags;
+ if (sscanf(data, "%30d", &amaflags) != 1) {
+ ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan));
+ ast_channel_unlock(chan);
+ return 0;
+ }
+ ast_channel_amaflags_set(chan, amaflags);
+ } else {
+ ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data));
+ }
ast_channel_unlock(chan);
return 0;
}
diff --git a/main/stasis.c b/main/stasis.c
index e810dd852..406a1bb25 100644
--- a/main/stasis.c
+++ b/main/stasis.c
@@ -32,6 +32,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
+#include "asterisk/stasis_internal.h"
#include "asterisk/stasis.h"
#include "asterisk/threadpool.h"
#include "asterisk/taskprocessor.h"
@@ -170,7 +171,7 @@ static void subscription_invoke(struct stasis_subscription *sub,
static void send_subscription_change_message(struct stasis_topic *topic, char *uniqueid, char *description);
-static struct stasis_subscription *__stasis_subscribe(
+struct stasis_subscription *internal_stasis_subscribe(
struct stasis_topic *topic,
stasis_subscription_cb callback,
void *data,
@@ -213,7 +214,7 @@ struct stasis_subscription *stasis_subscribe(
stasis_subscription_cb callback,
void *data)
{
- return __stasis_subscribe(topic, callback, data, 1);
+ return internal_stasis_subscribe(topic, callback, data, 1);
}
struct stasis_subscription *stasis_unsubscribe(struct stasis_subscription *sub)
@@ -476,7 +477,7 @@ struct stasis_subscription *stasis_forward_all(struct stasis_topic *from_topic,
* mailbox. Otherwise, messages forwarded to the same topic from
* different topics may get reordered. Which is bad.
*/
- sub = __stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0);
+ sub = internal_stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0);
if (sub) {
/* hold a ref to to_topic for this forwarding subscription */
ao2_ref(to_topic, +1);
diff --git a/main/stasis_cache.c b/main/stasis_cache.c
index 115bb7b67..5757c869a 100644
--- a/main/stasis_cache.c
+++ b/main/stasis_cache.c
@@ -33,6 +33,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/hashtab.h"
+#include "asterisk/stasis_internal.h"
#include "asterisk/stasis.h"
#include "asterisk/utils.h"
@@ -486,7 +487,7 @@ struct stasis_caching_topic *stasis_caching_topic_create(struct stasis_topic *or
caching_topic->id_fn = id_fn;
- sub = stasis_subscribe(original_topic, caching_topic_exec, caching_topic);
+ sub = internal_stasis_subscribe(original_topic, caching_topic_exec, caching_topic, 0);
if (sub == NULL) {
return NULL;
}
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 2a88b0068..e76f25824 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -156,10 +156,17 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
ast_string_field_set(snapshot, exten, ast_channel_exten(chan));
ast_string_field_set(snapshot, caller_name,
- S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""));
+ S_COR(ast_channel_caller(chan)->ani.name.valid, ast_channel_caller(chan)->ani.name.str,
+ S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "")));
ast_string_field_set(snapshot, caller_number,
- S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""));
-
+ S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str,
+ S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "")));
+ ast_string_field_set(snapshot, caller_dnid, S_OR(ast_channel_dialed(chan)->number.str, ""));
+ ast_string_field_set(snapshot, caller_subaddr,
+ S_COR(ast_channel_caller(chan)->ani.subaddress.valid, ast_channel_caller(chan)->ani.subaddress.str,
+ S_COR(ast_channel_caller(chan)->id.subaddress.valid, ast_channel_caller(chan)->id.subaddress.str, "")));
+ ast_string_field_set(snapshot, dialed_subaddr,
+ S_COR(ast_channel_dialed(chan)->subaddress.valid, ast_channel_dialed(chan)->subaddress.str, ""));
ast_string_field_set(snapshot, caller_ani,
S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, ""));
ast_string_field_set(snapshot, caller_rdnis,
@@ -493,20 +500,6 @@ struct ast_json *ast_multi_channel_blob_get_json(struct ast_multi_channel_blob *
return obj->blob;
}
-void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob)
-{
- RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-
- if (!blob) {
- blob = ast_json_null();
- }
-
- message = ast_channel_blob_create(chan, type, blob);
- if (message) {
- stasis_publish(ast_channel_topic(chan), message);
- }
-}
-
void ast_channel_publish_snapshot(struct ast_channel *chan)
{
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -526,6 +519,20 @@ void ast_channel_publish_snapshot(struct ast_channel *chan)
stasis_publish(ast_channel_topic(chan), message);
}
+void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob)
+{
+ RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+ if (!blob) {
+ blob = ast_json_null();
+ }
+
+ message = ast_channel_blob_create(chan, type, blob);
+ if (message) {
+ stasis_publish(ast_channel_topic(chan), message);
+ }
+}
+
void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
{
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
diff --git a/main/test.c b/main/test.c
index 2109c9478..fdc4916e1 100644
--- a/main/test.c
+++ b/main/test.c
@@ -48,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include "asterisk/stasis.h"
#include "asterisk/json.h"
#include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/json.h"
/*! \since 12
* \brief The topic for test suite messages
@@ -80,9 +82,11 @@ struct ast_test {
* CLI in addition to being saved off in status_str.
*/
struct ast_cli_args *cli;
- enum ast_test_result_state state; /*!< current test state */
- unsigned int time; /*!< time in ms test took */
- ast_test_cb_t *cb; /*!< test callback function */
+ enum ast_test_result_state state; /*!< current test state */
+ unsigned int time; /*!< time in ms test took */
+ ast_test_cb_t *cb; /*!< test callback function */
+ ast_test_init_cb_t *init_cb; /*!< test init function */
+ ast_test_cleanup_cb_t *cleanup_cb; /*!< test cleanup function */
AST_LIST_ENTRY(ast_test) entry;
};
@@ -159,6 +163,40 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc
return 0;
}
+int ast_test_register_init(const char *category, ast_test_init_cb_t *cb)
+{
+ struct ast_test *test;
+ int registered = 1;
+
+ AST_LIST_LOCK(&tests);
+ AST_LIST_TRAVERSE(&tests, test, entry) {
+ if (!(test_cat_cmp(test->info.category, category))) {
+ test->init_cb = cb;
+ registered = 0;
+ }
+ }
+ AST_LIST_UNLOCK(&tests);
+
+ return registered;
+}
+
+int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
+{
+ struct ast_test *test;
+ int registered = 1;
+
+ AST_LIST_LOCK(&tests);
+ AST_LIST_TRAVERSE(&tests, test, entry) {
+ if (!(test_cat_cmp(test->info.category, category))) {
+ test->cleanup_cb = cb;
+ registered = 0;
+ }
+ }
+ AST_LIST_UNLOCK(&tests);
+
+ return registered;
+}
+
int ast_test_register(ast_test_cb_t *cb)
{
struct ast_test *test;
@@ -203,14 +241,34 @@ int ast_test_unregister(ast_test_cb_t *cb)
static void test_execute(struct ast_test *test)
{
struct timeval begin;
+ enum ast_test_result_state result;
ast_str_reset(test->status_str);
begin = ast_tvnow();
- test->state = test->cb(&test->info, TEST_EXECUTE, test);
+ if (test->init_cb && test->init_cb(&test->info, test)) {
+ test->state = AST_TEST_FAIL;
+ goto exit;
+ }
+ result = test->cb(&test->info, TEST_EXECUTE, test);
+ if (test->state != AST_TEST_FAIL) {
+ test->state = result;
+ }
+ if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
+ test->state = AST_TEST_FAIL;
+ }
+exit:
test->time = ast_tvdiff_ms(ast_tvnow(), begin);
}
+void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
+{
+ if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
+ return;
+ }
+ test->state = state;
+}
+
static void test_xml_entry(struct ast_test *test, FILE *f)
{
if (!f || !test || test->state == AST_TEST_NOT_RUN) {
diff --git a/main/utils.c b/main/utils.c
index fde9b953b..100725487 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1546,6 +1546,15 @@ int ast_remaining_ms(struct timeval start, int max_ms)
return ms;
}
+void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length)
+{
+ int durh, durm, durs;
+ durh = duration / 3600;
+ durm = (duration % 3600) / 60;
+ durs = duration % 60;
+ snprintf(buf, length, "%02d:%02d:%02d", durh, durm, durs);
+}
+
#undef ONE_MILLION
#ifndef linux