summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Jordan <mjordan@digium.com>2013-06-17 03:00:38 +0000
committerMatthew Jordan <mjordan@digium.com>2013-06-17 03:00:38 +0000
commit6258bbe7bd1885ac5dec095ed0c4490c83a99f44 (patch)
treeff2794f730ca55903a09b9fe7f73f45169a71386
parent67e35c7b4748c3cef954820a2b182e2a5edf8d98 (diff)
Update Asterisk's CDRs for the new bridging framework
This patch is the initial push to update Asterisk's CDR engine for the new bridging framework. This patch guts the existing CDR engine and builds the new on top of messages coming across Stasis. As changes in channel state and bridge state are detected, CDRs are built and dispatched accordingly. This fundamentally changes CDRs in a few ways. (1) CDRs are now *very* reflective of the actual state of channels and bridges. This means CDRs track well with what an actual channel is doing - which is useful in transfer scenarios (which were previously difficult to pin down). It does, however, mean that CDRs cannot be 'fooled'. Previous behavior in Asterisk allowed for CDR applications, channels, and other properties to be spoofed in parts of the code - this no longer works. (2) CDRs have defined behavior in multi-party scenarios. This behavior will not be what everyone wants, but it is a defined behavior and as such, it is predictable. (3) The CDR manipulation functions and applications have been overhauled. Major changes have been made to ResetCDR and ForkCDR in particular. Many of the options for these two applications no longer made any sense with the new framework and the (slightly) more immutable nature of CDRs. There are a plethora of other changes. For a full description of CDR behavior, see the CDR specification on the Asterisk wiki. (closes issue ASTERISK-21196) Review: https://reviewboard.asterisk.org/r/2486/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@391947 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-rw-r--r--CHANGES267
-rw-r--r--UPGRADE.txt55
-rw-r--r--addons/cdr_mysql.c6
-rw-r--r--addons/chan_ooh323.c12
-rw-r--r--apps/app_authenticate.c8
-rw-r--r--apps/app_cdr.c105
-rw-r--r--apps/app_dial.c76
-rw-r--r--apps/app_disa.c8
-rw-r--r--apps/app_dumpchan.c13
-rw-r--r--apps/app_followme.c46
-rw-r--r--apps/app_forkcdr.c222
-rw-r--r--apps/app_osplookup.c23
-rw-r--r--apps/app_queue.c127
-rw-r--r--cdr/cdr_adaptive_odbc.c6
-rw-r--r--cdr/cdr_csv.c5
-rw-r--r--cdr/cdr_custom.c12
-rw-r--r--cdr/cdr_manager.c2
-rw-r--r--cdr/cdr_odbc.c9
-rw-r--r--cdr/cdr_pgsql.c12
-rw-r--r--cdr/cdr_radius.c4
-rw-r--r--cdr/cdr_syslog.c14
-rw-r--r--cdr/cdr_tds.c8
-rw-r--r--cel/cel_manager.c2
-rw-r--r--cel/cel_radius.c2
-rw-r--r--cel/cel_tds.c2
-rw-r--r--channels/chan_agent.c37
-rw-r--r--channels/chan_dahdi.c3
-rw-r--r--channels/chan_h323.c2
-rw-r--r--channels/chan_iax2.c7
-rw-r--r--channels/chan_mgcp.c3
-rw-r--r--channels/chan_sip.c29
-rw-r--r--channels/chan_skinny.c7
-rw-r--r--channels/chan_unistim.c2
-rw-r--r--funcs/func_callerid.c24
-rw-r--r--funcs/func_cdr.c317
-rw-r--r--funcs/func_channel.c11
-rw-r--r--include/asterisk/bridging.h31
-rw-r--r--include/asterisk/cdr.h649
-rw-r--r--include/asterisk/cel.h25
-rw-r--r--include/asterisk/channel.h111
-rw-r--r--include/asterisk/channel_internal.h4
-rw-r--r--include/asterisk/stasis_channels.h60
-rw-r--r--include/asterisk/stasis_internal.h69
-rw-r--r--include/asterisk/test.h73
-rw-r--r--include/asterisk/time.h11
-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
-rw-r--r--res/res_agi.c5
-rw-r--r--res/res_config_sqlite.c4
-rw-r--r--res/res_monitor.c10
-rw-r--r--res/res_stasis_answer.c3
-rw-r--r--tests/test_cdr.c2413
68 files changed, 7108 insertions, 2646 deletions
diff --git a/CHANGES b/CHANGES
index f3d420bc8..b81c2da28 100644
--- a/CHANGES
+++ b/CHANGES
@@ -11,6 +11,94 @@
--- Functionality changes from Asterisk 11 to Asterisk 12 --------------------
------------------------------------------------------------------------------
+Applications
+------------------
+
+AgentMonitorOutgoing
+------------------
+ * The 'c' option has been removed. It is not possible to modify the name of a
+ channel involved in a CDR.
+
+ForkCDR
+------------------
+ * ForkCDR no longer automatically resets the forked CDR. See the 'r' option
+ for more information.
+
+ * Variables are no longer purged from the original CDR. See the 'v' option for
+ more information.
+
+ * The 'A' option has been removed. The Answer time on a CDR is never updated
+ once set.
+
+ * The 'd' option has been removed. The disposition on a CDR is a function of
+ the state of the channel and cannot be altered.
+
+ * The 'D' option has been removed. Who the Party B is on a CDR is a function
+ of the state of the respective channels, and cannot be altered.
+
+ * The 'r' option has been changed. Previously, ForkCDR always reset the CDR
+ such that the start time and, if applicable, the answer time was updated.
+ Now, by default, ForkCDR simply forks the CDR, maintaining any times. The
+ 'r' option now triggers the Reset, setting the start time (and answer time
+ if applicable) to the current time.
+
+ * The 's' option has been removed. A variable can be set on the original CDR
+ if desired using the CDR function, and removed from a forked CDR using the
+ same function.
+
+ * The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no
+ longer applies in the CDR engine.
+
+ * The 'v' option now prevents the copy of the variables from the original CDR
+ to the forked CDR. Previously the variables were always copied but were
+ removed from the original. Removing variables from a CDR can have unintended
+ side effects - this option allows the user to prevent propagation of
+ variables from the original to the forked without modifying the original.
+
+MeetMe
+-------------------
+* Added the 'n' option to MeetMe to prevent application of the DENOISE function
+ to a channel joining a conference. Some channel drivers that vary the number
+ of audio samples in a voice frame will experience significant quality problems
+ if a denoiser is attached to the channel; this option gives them the ability
+ to remove the denoiser without having to unload func_speex.
+
+NoCDR
+------------------
+ * The NoCDR application is deprecated. Please use the CDR_PROP function to
+ disable CDRs.
+ * While the NoCDR application will prevent CDRs for a channel from being
+ propagated to registered CDR backends, it will not prevent that data from
+ being collected. Hence, a subsequent call to ResetCDR or the CDR_PROP
+ function that enables CDRs on a channel will restore those records that have
+ not yet been finalized.
+
+Queue
+-------------------
+ * Add queue available hint. exten => 8501,hint,Queue:markq_avail
+ Note: the suffix '_avail' after the queuename.
+ Reports 'InUse' for no logged in agents or no free agents.
+ Reports 'Idle' when an agent is free.
+
+ResetCDR
+------------------
+ * The 'e' option has been deprecated. Use the CDR_PROP function to re-enable
+ CDRs when they were previously disabled on a channel.
+ * The 'w' and 'a' options have been removed. Dispatching CDRs to registered
+ backends occurs on an as-needed basis in order to preserve linkedid
+ propagation and other needed behavior.
+
+SetAMAFlags
+------------------
+ * This application is deprecated in favor of the CHANNEL function.
+
+
+Core
+------------------
+ * Redirecting reasons can now be set to arbitrary strings. This means
+ that the REDIRECTING dialplan function can be used to set the redirecting
+ reason to any string. It also allows for custom strings to be read as the
+ redirecting reason from SIP Diversion headers.
AMI (Asterisk Manager Interface)
------------------
@@ -72,6 +160,9 @@ AMI (Asterisk Manager Interface)
event, the various ChanVariable fields will contain a suffix that specifies
which channel they correspond to.
+* The NewPeerAccount AMI event is no longer raised. The NewAccountCode AMI
+ event always conveys the AMI event for a particular channel.
+
* All "Reload" events have been consolidated into a single event type. This
event will always contain a Module field specifying the name of the module
and a Status field denoting the result of the reload. All modules now issue
@@ -118,33 +209,21 @@ AGI (Asterisk Gateway Interface)
* The manager event AsyncAGI has been split into AsyncAGIStart, AsyncAGIExec,
and AsyncAGIEnd.
-Channel Drivers
-------------------
- * When a channel driver is configured to enable jiterbuffers, they are now
- applied unconditionally when a channel joins a bridge. If a jitterbuffer
- is already set for that channel when it enters, such as by the JITTERBUFFER
- function, then the existing jitterbuffer will be used and the one set by
- the channel driver will not be applied.
-
-chan_local
-------------------
- * The /b option is removed.
-
- * chan_local moved into the system core and is no longer a loadable module.
-
-chan_mobile
+CDR (Call Detail Records)
------------------
- * Added general support for busy detection.
+ * Significant changes have been made to the behavior of CDRs. For a full
+ definition of CDR behavior in Asterisk 12, please read the specification
+ on the Asterisk wiki (wiki.asterisk.org).
- * Added ECAM command support for Sony Ericsson phones.
+ * CDRs will now be created between all participants in a bridge. For each
+ pair of channels in a bridge, a CDR is created to represent the path of
+ communication between those two endpoints. This lets an end user choose who
+ to bill for what during multi-party bridges or bridge operations during
+ transfer scenarios.
-chan_sip
-------------------
- * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf
- using the 'supportpath' setting, either on a global basis or on a peer basis.
- This setting enables Asterisk to route outgoing out-of-dialog requests via a
- set of proxies by using a pre-loaded route-set defined by the Path headers in
- the REGISTER request. See Realtime updates for more configuration information.
+ * When a CDR is dispatched, user defined CDR variables from both parties are
+ included in the resulting CDR. If both parties have the same variable, only
+ the Party A value is provided.
Features
-------------------
@@ -163,12 +242,6 @@ Features
and FEATUREMAP() functions inherited to child channels by setting
FEATURE(inherit)=yes.
-Functions
-------------------
- * JITTERBUFFER now accepts an argument of 'disabled' which can be used
- to remove jitterbuffers previously set on a channel with JITTERBUFFER.
- The value of this setting is ignored when disabled is used for the argument.
-
Logging
-------------------
* When performing queue pause/unpause on an interface without specifying an
@@ -178,25 +251,12 @@ Logging
* Added the 'queue_log_realtime_use_gmt' option to have timestamps in GMT
for realtime queue log entries.
-MeetMe
--------------------
-* Added the 'n' option to MeetMe to prevent application of the DENOISE function
- to a channel joining a conference. Some channel drivers that vary the number
- of audio samples in a voice frame will experience significant quality problems
- if a denoiser is attached to the channel; this option gives them the ability
- to remove the denoiser without having to unload func_speex.
-
Parking
-------------------
* Parking is now implemented as a module instead of as core functionality.
The preferred way to configure parking is now through res_parking.conf while
configuration through features.conf is not currently supported.
- * res_parking uses the configuration framework. If an invalid configuration is
- supplied, res_parking will fail to load or fail to reload. Previously parking
- lots that were misconfigured would generally be accepted with certain
- configuration problems leading to individual disabled parking lots.
-
* Parked calls are now placed in bridges. This is a largely architectural change,
but it could have some implications in allowing for new parked call retrieval
methods and the contents of parking lots will be visible though certain bridge
@@ -236,63 +296,96 @@ Parking
by default. Instead, it will follow the timeout rules of the parking lot. The
old behavior can be reproduced by using the 'c' option.
- * Added a channel variable PARKER_FLAT which stores the name of the extension
- that would be used to come back to if comebacktoorigin was set to use. This can
- be useful when comebacktoorigin is off if you still want to use the extensions
- in the park-dial context that are generated to redial the parker on timeout.
+Realtime
+------------------
+ * Dynamic realtime tables for SIP Users can now include a 'path' field. This
+ will store the path information for that peer when it registers. Realtime
+ tables can also use the 'supportpath' field to enable Path header support.
-Queue
--------------------
- * Add queue available hint. exten => 8501,hint,Queue:markq_avail
- Note: the suffix '_avail' after the queuename.
- Reports 'InUse' for no logged in agents or no free agents.
- Reports 'Idle' when an agent is free.
+ * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport
+ objectIdentifier. This maps to the supportpath option in sip.conf.
+
+Sorcery
+------------------
+ * All future modules which utilize Sorcery for object persistence must have a
+ column named "id" within their schema when using the Sorcery realtime module.
+ This column must be able to contain a string of up to 128 characters in length.
- * The configuration options eventwhencalled and eventmemberstatus have been
- removed. As a result, the AMI events QueueMemberStatus, AgentCalled,
- AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be
- sent. The "Variable" fields will also no longer exist on the Agent* events.
-Core
+Channel Drivers
------------------
- * Redirecting reasons can now be set to arbitrary strings. This means
- that the REDIRECTING dialplan function can be used to set the redirecting
- reason to any string. It also allows for custom strings to be read as the
- redirecting reason from SIP Diversion headers.
+ * When a channel driver is configured to enable jiterbuffers, they are now
+ applied unconditionally when a channel joins a bridge. If a jitterbuffer
+ is already set for that channel when it enters, such as by the JITTERBUFFER
+ function, then the existing jitterbuffer will be used and the one set by
+ the channel driver will not be applied.
+
+chan_agent
+------------------
+ * The updatecdr option has been removed. Altering the names of channels on a
+ CDR is not supported - the name of the channel is the name of the channel,
+ and pretending otherwise helps no one.
+ * The AGENTUPDATECDR channel variable has also been removed, for the same
+ reason as the updatecdr option.
+
+chan_local
+------------------
+ * The /b option is removed.
- * For DTMF blind and attended transfers, the channel variable TRANSFER_CONTEXT
- must be on the channel initiating the transfer to have any effect.
+ * chan_local moved into the system core and is no longer a loadable module.
- * The channel variable ATTENDED_TRANSFER_COMPLETE_SOUND is no longer channel
- driver specific. If the channel variable is set on the transferrer channel,
- the sound will be played to the target of an attended transfer.
+chan_mobile
+------------------
+ * Added general support for busy detection.
- * The channel variable BRIDGEPEER becomes a comma separated list of peers in
- a multi-party bridge. The BRIDGEPEER value can have a maximum of 10 peers
- listed. Any more peers in the bridge will not be included in the list.
- BRIDGEPEER is not valid in holding bridges like parking since those channels
- do not talk to each other even though they are in a bridge.
+ * Added ECAM command support for Sony Ericsson phones.
- * The channel variable BRIDGEPVTCALLID is only valid for two party bridges
- and will contain a value if the BRIDGEPEER's channel driver supports it.
+chan_sip
+------------------
+ * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf
+ using the 'supportpath' setting, either on a global basis or on a peer basis.
+ This setting enables Asterisk to route outgoing out-of-dialog requests via a
+ set of proxies by using a pre-loaded route-set defined by the Path headers in
+ the REGISTER request. See Realtime updates for more configuration information.
- * The channel variable DYNAMIC_PEERNAME is redundant with BRIDGEPEER and is
- removed. The more useful DYNAMIC_WHO_ACTIVATED gives the channel name that
- activated the dynamic feature.
- * The channel variables DYNAMIC_FEATURENAME and DYNAMIC_WHO_ACTIVATED are set
- only on the channel executing the dynamic feature. Executing a dynamic
- feature on the bridge peer in a multi-party bridge will execute it on all
- peers of the activating channel.
+Functions
+------------------
-Realtime
+JITTERBUFFER
------------------
- * Dynamic realtime tables for SIP Users can now include a 'path' field. This
- will store the path information for that peer when it registers. Realtime
- tables can also use the 'supportpath' field to enable Path header support.
+ * JITTERBUFFER now accepts an argument of 'disabled' which can be used
+ to remove jitterbuffers previously set on a channel with JITTERBUFFER.
+ The value of this setting is ignored when disabled is used for the argument.
- * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport
- objectIdentifier. This maps to the supportpath option in sip.conf.
+CDR (function)
+------------------
+ * The 'amaflags' and 'accountcode' attributes for the CDR function are
+ deprecated. Use the CHANNEL function instead to access these attributes.
+ * The 'l' option has been removed. When reading a CDR attribute, the most
+ recent record is always used. When writing a CDR attribute, all non-finalized
+ CDRs are updated.
+ * The 'r' option has been removed, for the same reason as the 'l' option.
+ * The 's' option has been removed, as LOCKED semantics no longer exist in the
+ CDR engine.
+
+CDR_PROP
+------------------
+ * A new function CDR_PROP has been added. This function lets you set properties
+ on a channel's active CDRs. This function is write-only. Properties accept
+ boolean values to set/clear them on the channel's CDRs. Valid properties
+ include:
+ * 'party_a' - make this channel the preferred Party A in any CDR between two
+ channels. If two channels have this property set, the creation time of the
+ channel is used to determine who is Party A. Note that dialed channels are
+ never Party A in a CDR.
+ * 'disable' - disable CDRs on this channel. This is analogous to the NoCDR
+ application when set to True, and analogous to the 'e' option in ResetCDR
+ when set to False.
+
+
+Resources
+------------------
RTP
------------------
diff --git a/UPGRADE.txt b/UPGRADE.txt
index dfe808141..7a5261b94 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -21,6 +21,49 @@
===
===========================================================
+AgentMonitorOutgoing
+ - The 'c' option has been removed. It is not possible to modify the name of a
+ channel involved in a CDR.
+
+NoCDR:
+ - This application is deprecated. Please use the CDR_PROP function instead.
+
+ResetCDR:
+ - The 'w' and 'a' options have been removed. Dispatching CDRs to registered
+ backends occurs on an as-needed basis in order to preserve linkedid
+ propagation and other needed behavior.
+ - The 'e' option is deprecated. Please use the CDR_PROP function to enable
+ CDRs on a channel that they were previously disabled on.
+ - The ResetCDR application is no longer a part of core Asterisk, and instead
+ is now delivered as part of app_cdr.
+
+ForkCDR:
+ - ForkCDR no longer automatically resets the forked CDR. See the 'r' option
+ for more information.
+ - Variables are no longer purged from the original CDR. See the 'v' option for
+ more information.
+ - The 'A' option has been removed. The Answer time on a CDR is never updated
+ once set.
+ - The 'd' option has been removed. The disposition on a CDR is a function of
+ the state of the channel and cannot be altered.
+ - The 'D' option has been removed. Who the Party B is on a CDR is a function
+ of the state of the respective channels, and cannot be altered.
+ - The 'r' option has been changed. Previously, ForkCDR always reset the CDR
+ such that the start time and, if applicable, the answer time was updated.
+ Now, by default, ForkCDR simply forks the CDR, maintaining any times. The
+ 'r' option now triggers the Reset, setting the start time (and answer time
+ if applicable) to the current time.
+ - The 's' option has been removed. A variable can be set on the original CDR
+ if desired using the CDR function, and removed from a forked CDR using the
+ same function.
+ - The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no
+ longer applies in the CDR engine.
+ - The 'v' option now prevents the copy of the variables from the original CDR
+ to the forked CDR. Previously the variables were always copied but were
+ removed from the original. Removing variables from a CDR can have unintended
+ side effects - this option allows the user to prevent propagation of
+ variables from the original to the forked without modifying the original.
+
AMI:
- The SIP SIPqualifypeer action now sends a response indicating it will qualify
a peer once a peer has been found to qualify. Once the qualify has been
@@ -72,6 +115,16 @@ SendDTMF:
- Now recognizes 'W' to pause sending DTMF for one second in addition to
the previously existing 'w' that paused sending DTMF for half a second.
+SetAMAFlags
+ - This application is deprecated in favor of the CHANNEL function.
+
+chan_agent:
+ - The updatecdr option has been removed. Altering the names of channels on a
+ CDR is not supported - the name of the channel is the name of the channel,
+ and pretending otherwise helps no one.
+ - The AGENTUPDATECDR channel variable has also been removed, for the same
+ reason as the updatecdr option.
+
chan_dahdi:
- Analog port dialing and deferred DTMF dialing for PRI now distinguishes
between 'w' and 'W'. The 'w' pauses dialing for half a second. The 'W'
@@ -79,7 +132,7 @@ chan_dahdi:
- The default for inband_on_proceeding has changed to no.
chan_local:
- - The /b option is removed.
+ - The /b option has been removed.
Dialplan:
- All channel and global variable names are evaluated in a case-sensitive manner.
diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c
index b87d8c6aa..23e96c562 100644
--- a/addons/cdr_mysql.c
+++ b/addons/cdr_mysql.c
@@ -251,7 +251,7 @@ db_reconnect:
char timestr[128];
ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
- ast_cdr_setvar(cdr, "calldate", timestr, 0);
+ ast_cdr_setvar(cdr, "calldate", timestr);
cdrname = "calldate";
} else {
cdrname = "start";
@@ -277,9 +277,9 @@ db_reconnect:
strstr(entry->type, "real") ||
strstr(entry->type, "numeric") ||
strstr(entry->type, "fixed"))) {
- ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 1);
+ ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
} else {
- ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 0);
+ ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
}
if (value) {
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
index a4f62acd8..303b06857 100644
--- a/addons/chan_ooh323.c
+++ b/addons/chan_ooh323.c
@@ -2376,7 +2376,7 @@ static struct ooh323_user *build_user(const char *name, struct ast_variable *v)
ast_parse_allow_disallow(&user->prefs,
user->cap, tcodecs, 1);
} else if (!strcasecmp(v->name, "amaflags")) {
- user->amaflags = ast_cdr_amaflags2int(v->value);
+ user->amaflags = ast_channel_string2amaflag(v->value);
} else if (!strcasecmp(v->name, "ip") || !strcasecmp(v->name, "host")) {
struct ast_sockaddr p;
if (!ast_parse_arg(v->value, PARSE_ADDR, &p)) {
@@ -2560,7 +2560,7 @@ static struct ooh323_peer *build_peer(const char *name, struct ast_variable *v,
ast_parse_allow_disallow(&peer->prefs, peer->cap,
tcodecs, 1);
} else if (!strcasecmp(v->name, "amaflags")) {
- peer->amaflags = ast_cdr_amaflags2int(v->value);
+ peer->amaflags = ast_channel_string2amaflag(v->value);
} else if (!strcasecmp(v->name, "roundtrip")) {
sscanf(v->value, "%d,%d", &peer->rtdrcount, &peer->rtdrinterval);
} else if (!strcasecmp(v->name, "dtmfmode")) {
@@ -2917,7 +2917,7 @@ int reload_config(int reload)
"'lowdelay', 'throughput', 'reliability', "
"'mincost', or 'none'\n", v->lineno);
} else if (!strcasecmp(v->name, "amaflags")) {
- gAMAFLAGS = ast_cdr_amaflags2int(v->value);
+ gAMAFLAGS = ast_channel_string2amaflag(v->value);
} else if (!strcasecmp(v->name, "accountcode")) {
ast_copy_string(gAccountcode, v->value, sizeof(gAccountcode));
} else if (!strcasecmp(v->name, "disallow")) {
@@ -3117,7 +3117,7 @@ static char *handle_cli_ooh323_show_peer(struct ast_cli_entry *e, int cmd, struc
}
ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", peer->accountcode);
- ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(peer->amaflags));
+ ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(peer->amaflags));
ast_cli(a->fd, "%-15.15s%s\n", "IP:Port: ", ip_port);
ast_cli(a->fd, "%-15.15s%d\n", "OutgoingLimit: ", peer->outgoinglimit);
ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", peer->rtptimeout);
@@ -3276,7 +3276,7 @@ static char *handle_cli_ooh323_show_user(struct ast_cli_entry *e, int cmd, struc
}
ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", user->accountcode);
- ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(user->amaflags));
+ ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(user->amaflags));
ast_cli(a->fd, "%-15.15s%s\n", "Context: ", user->context);
ast_cli(a->fd, "%-15.15s%d\n", "IncomingLimit: ", user->incominglimit);
ast_cli(a->fd, "%-15.15s%d\n", "InUse: ", user->inUse);
@@ -3524,7 +3524,7 @@ static char *handle_cli_ooh323_show_config(struct ast_cli_entry *e, int cmd, str
ast_cli(a->fd, "%-20s%ld\n", "Call counter: ", callnumber);
ast_cli(a->fd, "%-20s%s\n", "AccountCode: ", gAccountcode);
- ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_cdr_flags2str(gAMAFLAGS));
+ ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_channel_amaflags2string(gAMAFLAGS));
pAlias = gAliasList;
if(pAlias) {
diff --git a/apps/app_authenticate.c b/apps/app_authenticate.c
index fbb430089..a8370588b 100644
--- a/apps/app_authenticate.c
+++ b/apps/app_authenticate.c
@@ -213,9 +213,9 @@ static int auth_exec(struct ast_channel *chan, const char *data)
continue;
ast_md5_hash(md5passwd, passwd);
if (!strcmp(md5passwd, md5secret)) {
- if (ast_test_flag(&flags,OPT_ACCOUNT)) {
+ if (ast_test_flag(&flags, OPT_ACCOUNT)) {
ast_channel_lock(chan);
- ast_cdr_setaccount(chan, buf);
+ ast_channel_accountcode_set(chan, buf);
ast_channel_unlock(chan);
}
break;
@@ -224,7 +224,7 @@ static int auth_exec(struct ast_channel *chan, const char *data)
if (!strcmp(passwd, buf)) {
if (ast_test_flag(&flags, OPT_ACCOUNT)) {
ast_channel_lock(chan);
- ast_cdr_setaccount(chan, buf);
+ ast_channel_accountcode_set(chan, buf);
ast_channel_unlock(chan);
}
break;
@@ -250,7 +250,7 @@ static int auth_exec(struct ast_channel *chan, const char *data)
if ((retries < 3) && !res) {
if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) {
ast_channel_lock(chan);
- ast_cdr_setaccount(chan, passwd);
+ ast_channel_accountcode_set(chan, passwd);
ast_channel_unlock(chan);
}
if (!(res = ast_streamfile(chan, "auth-thankyou", ast_channel_language(chan))))
diff --git a/apps/app_cdr.c b/apps/app_cdr.c
index 3c244712b..ba7139cf1 100644
--- a/apps/app_cdr.c
+++ b/apps/app_cdr.c
@@ -35,25 +35,114 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/module.h"
+#include "asterisk/app.h"
/*** DOCUMENTATION
<application name="NoCDR" language="en_US">
<synopsis>
- Tell Asterisk to not maintain a CDR for the current call
+ Tell Asterisk to not maintain a CDR for this channel.
</synopsis>
<syntax />
<description>
- <para>This application will tell Asterisk not to maintain a CDR for the current call.</para>
+ <para>This application will tell Asterisk not to maintain a CDR for
+ the current channel. This does <emphasis>NOT</emphasis> mean that
+ information is not tracked; rather, if the channel is hung up no
+ CDRs will be created for that channel.</para>
+ <para>If a subsequent call to ResetCDR occurs, all non-finalized
+ CDRs created for the channel will be enabled.</para>
+ <note><para>This application is deprecated. Please use the CDR_PROP
+ function to disable CDRs on a channel.</para></note>
</description>
+ <see-also>
+ <ref type="application">ResetCDR</ref>
+ <ref type="function">CDR_PROP</ref>
+ </see-also>
+ </application>
+ <application name="ResetCDR" language="en_US">
+ <synopsis>
+ Resets the Call Data Record.
+ </synopsis>
+ <syntax>
+ <parameter name="options">
+ <optionlist>
+ <option name="v">
+ <para>Save the CDR variables during the reset.</para>
+ </option>
+ <option name="e">
+ <para>Enable the CDRs for this channel only (negate
+ effects of NoCDR).</para>
+ </option>
+ </optionlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>This application causes the Call Data Record to be reset.
+ Depending on the flags passed in, this can have several effects.
+ With no options, a reset does the following:</para>
+ <para>1. The <literal>start</literal> time is set to the current time.</para>
+ <para>2. If the channel is answered, the <literal>answer</literal> time is set to the
+ current time.</para>
+ <para>3. All variables are wiped from the CDR. Note that this step
+ can be prevented with the <literal>v</literal> option.</para>
+ <para>On the other hand, if the <literal>e</literal> option is
+ specified, the effects of the NoCDR application will be lifted. CDRs
+ will be re-enabled for this channel.</para>
+ <note><para>The <literal>e</literal> option is deprecated. Please
+ use the CDR_PROP function instead.</para></note>
+ </description>
+ <see-also>
+ <ref type="application">ForkCDR</ref>
+ <ref type="application">NoCDR</ref>
+ <ref type="function">CDR_PROP</ref>
+ </see-also>
</application>
***/
static const char nocdr_app[] = "NoCDR";
+static const char resetcdr_app[] = "ResetCDR";
+
+enum reset_cdr_options {
+ OPT_DISABLE_DISPATCH = (1 << 0),
+ OPT_KEEP_VARS = (1 << 1),
+ OPT_ENABLE = (1 << 2),
+};
+
+AST_APP_OPTIONS(resetcdr_opts, {
+ AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
+ AST_APP_OPTION('e', AST_CDR_FLAG_DISABLE_ALL),
+});
+
+static int resetcdr_exec(struct ast_channel *chan, const char *data)
+{
+ char *args;
+ struct ast_flags flags = { 0 };
+ int res = 0;
+
+ if (!ast_strlen_zero(data)) {
+ args = ast_strdupa(data);
+ ast_app_parse_options(resetcdr_opts, &flags, NULL, args);
+ }
+
+ if (ast_test_flag(&flags, AST_CDR_FLAG_DISABLE_ALL)) {
+ if (ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+ res = 1;
+ }
+ }
+ if (ast_cdr_reset(ast_channel_name(chan), &flags)) {
+ res = 1;
+ }
+
+ if (res) {
+ ast_log(AST_LOG_WARNING, "Failed to reset CDR for channel %s\n", ast_channel_name(chan));
+ }
+ return res;
+}
static int nocdr_exec(struct ast_channel *chan, const char *data)
{
- if (ast_channel_cdr(chan))
- ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED);
+ if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+ ast_log(AST_LOG_WARNING, "Failed to disable CDR for channel %s\n", ast_channel_name(chan));
+ }
return 0;
}
@@ -65,8 +154,14 @@ static int unload_module(void)
static int load_module(void)
{
- if (ast_register_application_xml(nocdr_app, nocdr_exec))
+ int res = 0;
+
+ res |= ast_register_application_xml(nocdr_app, nocdr_exec);
+ res |= ast_register_application_xml(resetcdr_app, resetcdr_exec);
+
+ if (res) {
return AST_MODULE_LOAD_FAILURE;
+ }
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/apps/app_dial.c b/apps/app_dial.c
index b0caa5c6b..c75eb2d3a 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -55,7 +55,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/app.h"
#include "asterisk/causes.h"
#include "asterisk/rtp_engine.h"
-#include "asterisk/cdr.h"
#include "asterisk/manager.h"
#include "asterisk/privacy.h"
#include "asterisk/stringfields.h"
@@ -753,36 +752,20 @@ struct cause_args {
static void handle_cause(int cause, struct cause_args *num)
{
- struct ast_cdr *cdr = ast_channel_cdr(num->chan);
-
switch(cause) {
case AST_CAUSE_BUSY:
- if (cdr)
- ast_cdr_busy(cdr);
num->busy++;
break;
-
case AST_CAUSE_CONGESTION:
- if (cdr)
- ast_cdr_failed(cdr);
num->congestion++;
break;
-
case AST_CAUSE_NO_ROUTE_DESTINATION:
case AST_CAUSE_UNREGISTERED:
- if (cdr)
- ast_cdr_failed(cdr);
num->nochan++;
break;
-
case AST_CAUSE_NO_ANSWER:
- if (cdr) {
- ast_cdr_noanswer(cdr);
- }
- break;
case AST_CAUSE_NORMAL_CLEARING:
break;
-
default:
num->nochan++;
break;
@@ -974,7 +957,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
ast_channel_appl_set(c, "AppDial");
ast_channel_data_set(c, "(Outgoing Line)");
- ast_publish_channel_state(c);
+ ast_channel_publish_snapshot(c);
ast_channel_unlock(in);
if (single && !ast_test_flag64(o, OPT_IGNORE_CONNECTEDLINE)) {
@@ -1096,7 +1079,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
*/
*to = -1;
strcpy(pa->status, "CONGESTION");
- ast_cdr_failed(ast_channel_cdr(in));
ast_channel_publish_dial(in, outgoing->chan, NULL, pa->status);
return NULL;
}
@@ -1301,10 +1283,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
peer = c;
ast_channel_publish_dial(in, peer, NULL, "ANSWER");
publish_dial_end_event(in, out_chans, peer, "CANCEL");
- if (ast_channel_cdr(peer)) {
- ast_channel_cdr(peer)->answer = ast_tvnow();
- ast_channel_cdr(peer)->disposition = AST_CDR_ANSWERED;
- }
ast_copy_flags64(peerflags, o,
OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
@@ -1326,7 +1304,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
case AST_CONTROL_BUSY:
ast_verb(3, "%s is busy\n", ast_channel_name(c));
ast_channel_hangupcause_set(in, ast_channel_hangupcause(c));
- ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c)));
+ ast_channel_publish_dial(in, c, NULL, "BUSY");
ast_hangup(c);
c = o->chan = NULL;
ast_clear_flag64(o, DIAL_STILLGOING);
@@ -1335,7 +1313,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
case AST_CONTROL_CONGESTION:
ast_verb(3, "%s is circuit-busy\n", ast_channel_name(c));
ast_channel_hangupcause_set(in, ast_channel_hangupcause(c));
- ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c)));
+ ast_channel_publish_dial(in, c, NULL, "CONGESTION");
ast_hangup(c);
c = o->chan = NULL;
ast_clear_flag64(o, DIAL_STILLGOING);
@@ -1542,7 +1520,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
/* Got hung up */
*to = -1;
strcpy(pa->status, "CANCEL");
- ast_cdr_noanswer(ast_channel_cdr(in));
publish_dial_end_event(in, out_chans, NULL, pa->status);
if (f) {
if (f->data.uint32) {
@@ -1565,7 +1542,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
if (onedigit_goto(in, context, (char) f->subclass.integer, 1)) {
ast_verb(3, "User hit %c to disconnect call.\n", f->subclass.integer);
*to = 0;
- ast_cdr_noanswer(ast_channel_cdr(in));
*result = f->subclass.integer;
strcpy(pa->status, "CANCEL");
publish_dial_end_event(in, out_chans, NULL, pa->status);
@@ -1584,7 +1560,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
ast_verb(3, "User requested call disconnect.\n");
*to = 0;
strcpy(pa->status, "CANCEL");
- ast_cdr_noanswer(ast_channel_cdr(in));
publish_dial_end_event(in, out_chans, NULL, pa->status);
ast_frfree(f);
if (is_cc_recall) {
@@ -1679,13 +1654,10 @@ skip_frame:;
}
}
- if (!*to) {
+ if (!*to || ast_check_hangup(in)) {
ast_verb(3, "Nobody picked up in %d ms\n", orig);
publish_dial_end_event(in, out_chans, NULL, "NOANSWER");
}
- if (!*to || ast_check_hangup(in)) {
- ast_cdr_noanswer(ast_channel_cdr(in));
- }
#ifdef HAVE_EPOLL
AST_LIST_TRAVERSE(out_chans, epollo, node) {
@@ -1985,22 +1957,13 @@ static void end_bridge_callback(void *data)
time_t end;
struct ast_channel *chan = data;
- if (!ast_channel_cdr(chan)) {
- return;
- }
-
time(&end);
ast_channel_lock(chan);
- if (ast_channel_cdr(chan)->answer.tv_sec) {
- snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec);
- pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
- }
-
- if (ast_channel_cdr(chan)->start.tv_sec) {
- snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec);
- pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
- }
+ snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan));
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+ snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan));
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
ast_channel_unlock(chan);
}
@@ -2294,8 +2257,9 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
ast_channel_unlock(chan);
}
- if (ast_test_flag64(&opts, OPT_RESETCDR) && ast_channel_cdr(chan))
- ast_cdr_reset(ast_channel_cdr(chan), NULL);
+ if (ast_test_flag64(&opts, OPT_RESETCDR)) {
+ ast_cdr_reset(ast_channel_name(chan), 0);
+ }
if (ast_test_flag64(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY]))
opt_args[OPT_ARG_PRIVACY] = ast_strdupa(ast_channel_exten(chan));
@@ -2489,7 +2453,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
ast_channel_appl_set(tc, "AppDial");
ast_channel_data_set(tc, "(Outgoing Line)");
- ast_publish_channel_state(tc);
+ ast_channel_publish_snapshot(tc);
memset(ast_channel_whentohangup(tc), 0, sizeof(*ast_channel_whentohangup(tc)));
@@ -2620,10 +2584,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
res = ast_call(tmp->chan, tmp->number, 0); /* Place the call, but don't wait on the answer */
ast_channel_lock(chan);
- /* Save the info in cdr's that we called them */
- if (ast_channel_cdr(chan))
- ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(tmp->chan));
-
/* check the results of ast_call */
if (res) {
/* Again, keep going even if there's an error */
@@ -2738,10 +2698,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
conversation. */
hanguptree(&out_chans, peer, 1);
/* If appropriate, log that we have a destination channel and set the answer time */
- if (ast_channel_cdr(chan)) {
- ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
- ast_cdr_setanswer(ast_channel_cdr(chan), ast_channel_cdr(peer)->answer);
- }
if (ast_channel_name(peer))
pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", ast_channel_name(peer));
@@ -2836,10 +2792,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
}
if (chan && peer && ast_test_flag64(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) {
- /* chan and peer are going into the PBX, they both
- * should probably get CDR records. */
- ast_clear_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED);
- ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_DIALED);
+ /* chan and peer are going into the PBX; as such neither are considered
+ * outgoing channels any longer */
+ ast_clear_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+ ast_clear_flag(ast_channel_flags(peer), AST_FLAG_OUTGOING);
ast_replace_subargument_delimiter(opt_args[OPT_ARG_GOTO]);
ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]);
diff --git a/apps/app_disa.c b/apps/app_disa.c
index c43370c95..fe53772f1 100644
--- a/apps/app_disa.c
+++ b/apps/app_disa.c
@@ -362,7 +362,7 @@ static int disa_exec(struct ast_channel *chan, const char *data)
if (k == 3) {
int recheck = 0;
- struct ast_flags cdr_flags = { AST_CDR_FLAG_POSTED };
+ struct ast_flags cdr_flags = { AST_CDR_FLAG_DISABLE, };
if (!ast_exists_extension(chan, args.context, exten, 1,
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
@@ -384,8 +384,10 @@ static int disa_exec(struct ast_channel *chan, const char *data)
if (!ast_strlen_zero(acctcode))
ast_channel_accountcode_set(chan, acctcode);
- if (special_noanswer) cdr_flags.flags = 0;
- ast_cdr_reset(ast_channel_cdr(chan), &cdr_flags);
+ if (special_noanswer) {
+ ast_clear_flag(&cdr_flags, AST_CDR_FLAG_DISABLE);
+ }
+ ast_cdr_reset(ast_channel_name(chan), &cdr_flags);
ast_explicit_goto(chan, args.context, exten, 1);
return 0;
}
diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c
index 722f15541..7613832d4 100644
--- a/apps/app_dumpchan.c
+++ b/apps/app_dumpchan.c
@@ -70,7 +70,6 @@ static const char app[] = "DumpChan";
static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
{
- struct timeval now;
long elapsed_seconds = 0;
int hour = 0, min = 0, sec = 0;
char nf[256];
@@ -80,21 +79,19 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
struct ast_str *read_transpath = ast_str_alloca(256);
struct ast_bridge *bridge;
- now = ast_tvnow();
memset(buf, 0, size);
if (!c)
return 0;
- if (ast_channel_cdr(c)) {
- elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
- hour = elapsed_seconds / 3600;
- min = (elapsed_seconds % 3600) / 60;
- sec = elapsed_seconds % 60;
- }
+ elapsed_seconds = ast_channel_get_duration(c);
+ hour = elapsed_seconds / 3600;
+ min = (elapsed_seconds % 3600) / 60;
+ sec = elapsed_seconds % 60;
ast_channel_lock(c);
bridge = ast_channel_get_bridge(c);
ast_channel_unlock(c);
+
snprintf(buf,size,
"Name= %s\n"
"Type= %s\n"
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 66980009d..d12de3c1a 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -578,29 +578,6 @@ static void clear_caller(struct findme_user *tmpuser)
}
outbound = tmpuser->ochan;
- ast_channel_lock(outbound);
- if (!ast_channel_cdr(outbound)) {
- ast_channel_cdr_set(outbound, ast_cdr_alloc());
- if (ast_channel_cdr(outbound)) {
- ast_cdr_init(ast_channel_cdr(outbound), outbound);
- }
- }
- if (ast_channel_cdr(outbound)) {
- char tmp[256];
-
- snprintf(tmp, sizeof(tmp), "Local/%s", tmpuser->dialarg);
- ast_cdr_setapp(ast_channel_cdr(outbound), "FollowMe", tmp);
- ast_cdr_update(outbound);
- ast_cdr_start(ast_channel_cdr(outbound));
- ast_cdr_end(ast_channel_cdr(outbound));
- /* If the cause wasn't handled properly */
- if (ast_cdr_disposition(ast_channel_cdr(outbound), ast_channel_hangupcause(outbound))) {
- ast_cdr_failed(ast_channel_cdr(outbound));
- }
- } else {
- ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
- }
- ast_channel_unlock(outbound);
ast_hangup(outbound);
tmpuser->ochan = NULL;
}
@@ -1127,11 +1104,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
* Destoy all new outgoing calls.
*/
while ((tmpuser = AST_LIST_REMOVE_HEAD(&new_user_list, entry))) {
- ast_channel_lock(tmpuser->ochan);
- if (ast_channel_cdr(tmpuser->ochan)) {
- ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
- }
- ast_channel_unlock(tmpuser->ochan);
destroy_calling_node(tmpuser);
}
@@ -1153,11 +1125,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
AST_LIST_REMOVE_CURRENT(entry);
/* Destroy this failed new outgoing call. */
- ast_channel_lock(tmpuser->ochan);
- if (ast_channel_cdr(tmpuser->ochan)) {
- ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
- }
- ast_channel_unlock(tmpuser->ochan);
destroy_calling_node(tmpuser);
continue;
}
@@ -1310,15 +1277,10 @@ static void end_bridge_callback(void *data)
time(&end);
ast_channel_lock(chan);
- if (ast_channel_cdr(chan)->answer.tv_sec) {
- snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec);
- pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
- }
-
- if (ast_channel_cdr(chan)->start.tv_sec) {
- snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec);
- pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
- }
+ snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan));
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+ snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan));
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
ast_channel_unlock(chan);
}
diff --git a/apps/app_forkcdr.c b/apps/app_forkcdr.c
index 354792fb9..6231d381f 100644
--- a/apps/app_forkcdr.c
+++ b/apps/app_forkcdr.c
@@ -44,98 +44,46 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
/*** DOCUMENTATION
<application name="ForkCDR" language="en_US">
<synopsis>
- Forks the Call Data Record.
+ Forks the current Call Data Record for this channel.
</synopsis>
<syntax>
<parameter name="options">
<optionlist>
<option name="a">
- <para>Update the answer time on the NEW CDR just after it's been inited.
- The new CDR may have been answered already. The reset that forkcdr does
- will erase the answer time. This will bring it back, but the answer time
- will be a copy of the fork/start time. It will only do this if the initial
- cdr was indeed already answered.</para>
- </option>
- <option name="A">
- <para>Lock the original CDR against the answer time being updated. This
- will allow the disposition on the original CDR to remain the same.</para>
- </option>
- <option name="d">
- <para>Copy the disposition forward from the old cdr, after the init.</para>
- </option>
- <option name="D">
- <para>Clear the <literal>dstchannel</literal> on the new CDR after
- reset.</para>
+ <para>If the channel is answered, set the answer time on
+ the forked CDR to the current time. If this option is
+ not used, the answer time on the forked CDR will be the
+ answer time on the original CDR. If the channel is not
+ answered, this option has no effect.</para>
+ <para>Note that this option is implicitly assumed if the
+ <literal>r</literal> option is used.</para>
</option>
<option name="e">
- <para>End the original CDR. Do this after all the necessary data is copied
- from the original CDR to the new forked CDR.</para>
+ <para>End (finalize) the original CDR.</para>
</option>
<option name="r">
- <para>Do <emphasis>NOT</emphasis> reset the new cdr.</para>
- </option>
- <option name="s(name=val)">
- <para>Set the CDR var <replaceable>name</replaceable> in the original CDR,
- with value <replaceable>val</replaceable>.</para>
- </option>
- <option name="T">
- <para>Mark the original CDR with a DONT_TOUCH flag. setvar, answer, and end
- cdr funcs will obey this flag; normally they don't honor the LOCKED flag
- set on the original CDR record.</para>
- <note><para>Using this flag may cause CDR's not to have their end times
- updated! It is suggested that if you specify this flag, you might wish
- to use the <literal>e</literal> flag as well!.</para></note>
+ <para>Reset the start and answer times on the forked CDR.
+ This will set the start and answer times (if the channel
+ is answered) to be set to the current time.</para>
+ <para>Note that this option implicitly assumes the
+ <literal>a</literal> option.</para>
</option>
<option name="v">
- <para>When the new CDR is forked, it gets a copy of the vars attached to
- the current CDR. The vars attached to the original CDR are removed unless
- this option is specified.</para>
+ <para>Do not copy CDR variables and attributes from the
+ original CDR to the forked CDR.</para>
+ <warning><para>This option has changed. Previously, the
+ variables were removed from the original CDR. This no
+ longer occurs - this option now controls whether or not
+ a forked CDR inherits the variables from the original
+ CDR.</para></warning>
</option>
</optionlist>
</parameter>
</syntax>
<description>
- <para> Causes the Call Data Record to fork an additional cdr record starting from the time
- of the fork call. This new cdr record will be linked to end of the list of cdr records attached
- to the channel. The original CDR has a LOCKED flag set, which forces most cdr operations to skip
- it, except for the functions that set the answer and end times, which ignore the LOCKED flag. This
- allows all the cdr records in the channel to be 'ended' together when the channel is closed.</para>
- <para>The CDR() func (when setting CDR values) normally ignores the LOCKED flag also, but has options
- to vary its behavior. The 'T' option (described below), can override this behavior, but beware
- the risks.</para>
- <para>First, this app finds the last cdr record in the list, and makes a copy of it. This new copy
- will be the newly forked cdr record. Next, this new record is linked to the end of the cdr record list.
- Next, The new cdr record is RESET (unless you use an option to prevent this)</para>
- <para>This means that:</para>
- <para> 1. All flags are unset on the cdr record</para>
- <para> 2. the start, end, and answer times are all set to zero.</para>
- <para> 3. the billsec and duration fields are set to zero.</para>
- <para> 4. the start time is set to the current time.</para>
- <para> 5. the disposition is set to NULL.</para>
- <para>Next, unless you specified the <literal>v</literal> option, all variables will be removed from
- the original cdr record. Thus, the <literal>v</literal> option allows any CDR variables to be replicated
- to all new forked cdr records. Without the <literal>v</literal> option, the variables on the original
- are effectively moved to the new forked cdr record.</para>
- <para>Next, if the <literal>s</literal> option is set, the provided variable and value are set on the
- original cdr record.</para>
- <para>Next, if the <literal>a</literal> option is given, and the original cdr record has an answer time
- set, then the new forked cdr record will have its answer time set to its start time. If the old answer
- time were carried forward, the answer time would be earlier than the start time, giving strange
- duration and billsec times.</para>
- <para>If the <literal>d</literal> option was specified, the disposition is copied from
- the original cdr record to the new forked cdr. If the <literal>D</literal> option was specified,
- the destination channel field in the new forked CDR is erased. If the <literal>e</literal> option
- was specified, the 'end' time for the original cdr record is set to the current time. Future hang-up or
- ending events will not override this time stamp. If the <literal>A</literal> option is specified,
- the original cdr record will have it ANS_LOCKED flag set, which prevent future answer events from updating
- the original cdr record's disposition. Normally, an <literal>ANSWERED</literal> event would mark all cdr
- records in the chain as <literal>ANSWERED</literal>. If the <literal>T</literal> option is specified,
- the original cdr record will have its <literal>DONT_TOUCH</literal> flag set, which will force the
- cdr_answer, cdr_end, and cdr_setvar functions to leave that cdr record alone.</para>
- <para>And, last but not least, the original cdr record has its LOCKED flag set. Almost all internal
- CDR functions (except for the funcs that set the end, and answer times, and set a variable) will honor
- this flag and leave a LOCKED cdr record alone. This means that the newly created forked cdr record
- will be affected by events transpiring within Asterisk, with the previously noted exceptions.</para>
+ <para>Causes the Call Data Record engine to fork a new CDR starting
+ from the time the application is executed. The forked CDR will be
+ linked to the end of the CDRs associated with the channel.</para>
</description>
<see-also>
<ref type="function">CDR</ref>
@@ -147,126 +95,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static char *app = "ForkCDR";
-enum {
- OPT_SETANS = (1 << 0),
- OPT_SETDISP = (1 << 1),
- OPT_RESETDEST = (1 << 2),
- OPT_ENDCDR = (1 << 3),
- OPT_NORESET = (1 << 4),
- OPT_KEEPVARS = (1 << 5),
- OPT_VARSET = (1 << 6),
- OPT_ANSLOCK = (1 << 7),
- OPT_DONTOUCH = (1 << 8),
-};
-
-enum {
- OPT_ARG_VARSET = 0,
- /* note: this entry _MUST_ be the last one in the enum */
- OPT_ARG_ARRAY_SIZE,
-};
-
AST_APP_OPTIONS(forkcdr_exec_options, {
- AST_APP_OPTION('a', OPT_SETANS),
- AST_APP_OPTION('A', OPT_ANSLOCK),
- AST_APP_OPTION('d', OPT_SETDISP),
- AST_APP_OPTION('D', OPT_RESETDEST),
- AST_APP_OPTION('e', OPT_ENDCDR),
- AST_APP_OPTION('R', OPT_NORESET),
- AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET),
- AST_APP_OPTION('T', OPT_DONTOUCH),
- AST_APP_OPTION('v', OPT_KEEPVARS),
+ AST_APP_OPTION('a', AST_CDR_FLAG_SET_ANSWER),
+ AST_APP_OPTION('e', AST_CDR_FLAG_FINALIZE),
+ AST_APP_OPTION('r', AST_CDR_FLAG_RESET),
+ AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
});
-static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set)
-{
- struct ast_cdr *cdr;
- struct ast_cdr *newcdr;
- struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS };
-
- cdr = ast_channel_cdr(chan);
-
- while (cdr->next)
- cdr = cdr->next;
-
- if (!(newcdr = ast_cdr_dup_unique(cdr)))
- return;
-
- /*
- * End the original CDR if requested BEFORE appending the new CDR
- * otherwise we incorrectly end the new CDR also.
- */
- if (ast_test_flag(&optflags, OPT_ENDCDR)) {
- ast_cdr_end(cdr);
- }
-
- ast_cdr_append(cdr, newcdr);
-
- if (!ast_test_flag(&optflags, OPT_NORESET))
- ast_cdr_reset(newcdr, &flags);
-
- if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS))
- ast_cdr_free_vars(cdr, 0);
-
- if (!ast_strlen_zero(set)) {
- char *varname = ast_strdupa(set), *varval;
- varval = strchr(varname,'=');
- if (varval) {
- *varval = 0;
- varval++;
- ast_cdr_setvar(cdr, varname, varval, 0);
- }
- }
-
- if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer))
- newcdr->answer = newcdr->start;
-
- if (ast_test_flag(&optflags, OPT_SETDISP))
- newcdr->disposition = cdr->disposition;
-
- if (ast_test_flag(&optflags, OPT_RESETDEST))
- newcdr->dstchannel[0] = 0;
-
- if (ast_test_flag(&optflags, OPT_ANSLOCK))
- ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED);
-
- if (ast_test_flag(&optflags, OPT_DONTOUCH))
- ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH);
-
- ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED);
-}
-
static int forkcdr_exec(struct ast_channel *chan, const char *data)
{
- int res = 0;
- char *argcopy = NULL;
- struct ast_flags flags = {0};
- char *opts[OPT_ARG_ARRAY_SIZE];
- AST_DECLARE_APP_ARGS(arglist,
+ char *parse;
+ struct ast_flags flags = { 0, };
+ AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(options);
);
- if (!ast_channel_cdr(chan)) {
- ast_log(LOG_WARNING, "Channel does not have a CDR\n");
- return 0;
- }
-
- argcopy = ast_strdupa(data);
+ parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(arglist, argcopy);
+ AST_STANDARD_APP_ARGS(args, parse);
- opts[OPT_ARG_VARSET] = 0;
-
- if (!ast_strlen_zero(arglist.options))
- ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options);
+ if (!ast_strlen_zero(args.options)) {
+ ast_app_parse_options(forkcdr_exec_options, &flags, NULL, args.options);
+ }
- if (!ast_strlen_zero(data)) {
- int keepvars = ast_test_flag(&flags, OPT_KEEPVARS) ? 1 : 0;
- ast_set2_flag(ast_channel_cdr(chan), keepvars, AST_CDR_FLAG_KEEP_VARS);
+ if (ast_cdr_fork(ast_channel_name(chan), &flags)) {
+ ast_log(AST_LOG_WARNING, "Failed to fork CDR for channel %s\n", ast_channel_name(chan));
}
-
- ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]);
- return res;
+ return 0;
}
static int unload_module(void)
diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c
index b37a2ae63..5ab497f5e 100644
--- a/apps/app_osplookup.c
+++ b/apps/app_osplookup.c
@@ -2814,7 +2814,7 @@ static int ospfinished_exec(
int inhandle = OSP_INVALID_HANDLE;
int outhandle = OSP_INVALID_HANDLE;
int recorded = 0;
- time_t start, connect, end;
+ time_t start = 0, connect = 0, end = 0;
unsigned int release;
char buffer[OSP_SIZE_INTSTR];
char inqos[OSP_SIZE_QOSSTR] = { 0 };
@@ -2866,19 +2866,18 @@ static int ospfinished_exec(
}
ast_debug(1, "OSPFinish: cause '%d'\n", cause);
- if (ast_channel_cdr(chan)) {
- start = ast_channel_cdr(chan)->start.tv_sec;
- connect = ast_channel_cdr(chan)->answer.tv_sec;
- if (connect) {
- end = time(NULL);
- } else {
- end = connect;
- }
+ if (!ast_tvzero(ast_channel_creationtime(chan))) {
+ start = ast_channel_creationtime(chan).tv_sec;
+ }
+ if (!ast_tvzero(ast_channel_answertime(chan))) {
+ connect = ast_channel_answertime(chan).tv_sec;
+ }
+ if (connect) {
+ end = time(NULL);
} else {
- start = 0;
- connect = 0;
- end = 0;
+ end = connect;
}
+
ast_debug(1, "OSPFinish: start '%ld'\n", start);
ast_debug(1, "OSPFinish: connect '%ld'\n", connect);
ast_debug(1, "OSPFinish: end '%ld'\n", end);
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 4c9658313..724ea47ff 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -3666,10 +3666,10 @@ static void publish_dial_end_event(struct ast_channel *in, struct callattempt *o
struct callattempt *cur;
for (cur = outgoing; cur; cur = cur->q_next) {
- if (cur->chan && cur->chan != exception) {
+ if (cur->chan && cur->chan != exception) {
ast_channel_publish_dial(in, cur->chan, NULL, status);
- }
- }
+ }
+ }
}
/*! \brief Hang up a list of outgoing calls */
@@ -3931,9 +3931,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
member_call_pending_clear(tmp->member);
- if (ast_channel_cdr(qe->chan)) {
- ast_cdr_busy(ast_channel_cdr(qe->chan));
- }
+ /* BUGBUG: Raise a BUSY dial end message here */
tmp->stillgoing = 0;
++*busies;
return 0;
@@ -3987,21 +3985,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
} else {
ast_channel_exten_set(tmp->chan, ast_channel_exten(qe->chan));
}
- if (ast_cdr_isset_unanswered()) {
- /* they want to see the unanswered dial attempts! */
- /* set up the CDR fields on all the CDRs to give sensical information */
- ast_cdr_setdestchan(ast_channel_cdr(tmp->chan), ast_channel_name(tmp->chan));
- strcpy(ast_channel_cdr(tmp->chan)->clid, ast_channel_cdr(qe->chan)->clid);
- strcpy(ast_channel_cdr(tmp->chan)->channel, ast_channel_cdr(qe->chan)->channel);
- strcpy(ast_channel_cdr(tmp->chan)->src, ast_channel_cdr(qe->chan)->src);
- strcpy(ast_channel_cdr(tmp->chan)->dst, ast_channel_exten(qe->chan));
- strcpy(ast_channel_cdr(tmp->chan)->dcontext, ast_channel_context(qe->chan));
- strcpy(ast_channel_cdr(tmp->chan)->lastapp, ast_channel_cdr(qe->chan)->lastapp);
- strcpy(ast_channel_cdr(tmp->chan)->lastdata, ast_channel_cdr(qe->chan)->lastdata);
- ast_channel_cdr(tmp->chan)->amaflags = ast_channel_cdr(qe->chan)->amaflags;
- strcpy(ast_channel_cdr(tmp->chan)->accountcode, ast_channel_cdr(qe->chan)->accountcode);
- strcpy(ast_channel_cdr(tmp->chan)->userfield, ast_channel_cdr(qe->chan)->userfield);
- }
ast_channel_unlock(tmp->chan);
ast_channel_unlock(qe->chan);
@@ -4371,14 +4354,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
if (pos == 1 /* not found */) {
if (numlines == (numbusies + numnochan)) {
ast_debug(1, "Everyone is busy at this time\n");
- if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
- ast_cdr_busy(ast_channel_cdr(in));
- }
+ /* BUGBUG: We shouldn't have to set anything here, as each
+ * individual dial attempt should have set that CDR to busy
+ */
} else {
ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan);
- if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
- ast_cdr_failed(ast_channel_cdr(in));
- }
+ /* BUGBUG: We shouldn't have to set anything here, as each
+ * individual dial attempt should have set that CDR to busy
+ */
}
*to = 0;
return NULL;
@@ -4609,9 +4592,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
break;
case AST_CONTROL_BUSY:
ast_verb(3, "%s is busy\n", ochan_name);
- if (ast_channel_cdr(in)) {
- ast_cdr_busy(ast_channel_cdr(in));
- }
ast_channel_publish_dial(qe->chan, o->chan, on, "BUSY");
do_hang(o);
endtime = (long) time(NULL);
@@ -4631,9 +4611,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
break;
case AST_CONTROL_CONGESTION:
ast_verb(3, "%s is circuit-busy\n", ochan_name);
- if (ast_channel_cdr(in)) {
- ast_cdr_failed(ast_channel_cdr(in));
- }
ast_channel_publish_dial(qe->chan, o->chan, on, "CONGESTION");
endtime = (long) time(NULL);
endtime -= starttime;
@@ -4769,9 +4746,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
*to = 0;
publish_dial_end_event(in, outgoing, NULL, "CANCEL");
ast_frfree(f);
- if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
- ast_cdr_noanswer(ast_channel_cdr(in));
- }
return NULL;
}
if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass.integer)) {
@@ -4780,9 +4754,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
publish_dial_end_event(in, outgoing, NULL, "CANCEL");
*digit = f->subclass.integer;
ast_frfree(f);
- if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
- ast_cdr_noanswer(ast_channel_cdr(in));
- }
return NULL;
}
@@ -4839,11 +4810,6 @@ skip_frame:;
}
publish_dial_end_event(qe->chan, outgoing, NULL, "NOANSWER");
- if (ast_channel_cdr(in)
- && ast_channel_state(in) != AST_STATE_UP
- && (!*to || ast_check_hangup(in))) {
- ast_cdr_noanswer(ast_channel_cdr(in));
- }
}
#ifdef HAVE_EPOLL
@@ -5678,20 +5644,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
if (res == -1) {
ast_debug(1, "%s: Nobody answered.\n", ast_channel_name(qe->chan));
}
- if (ast_cdr_isset_unanswered()) {
- /* channel contains the name of one of the outgoing channels
- in its CDR; zero out this CDR to avoid a dual-posting */
- struct callattempt *o;
- for (o = outgoing; o; o = o->q_next) {
- if (!o->chan) {
- continue;
- }
- if (strcmp(ast_channel_cdr(o->chan)->dstchannel, ast_channel_cdr(qe->chan)->dstchannel) == 0) {
- ast_set_flag(ast_channel_cdr(o->chan), AST_CDR_FLAG_POST_DISABLED);
- break;
- }
- }
- }
} else { /* peer is valid */
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
/* Ah ha! Someone answered within the desired timeframe. Of course after this
@@ -5785,17 +5737,14 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
} else {
ast_moh_stop(qe->chan);
}
- /* If appropriate, log that we have a destination channel */
- if (ast_channel_cdr(qe->chan)) {
- ast_cdr_setdestchan(ast_channel_cdr(qe->chan), ast_channel_name(peer));
- }
+
/* Make sure channels are compatible */
res = ast_channel_make_compatible(qe->chan, peer);
if (res < 0) {
ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "SYSCOMPAT", "%s", "");
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(qe->chan), ast_channel_name(peer));
record_abandoned(qe);
- ast_cdr_failed(ast_channel_cdr(qe->chan));
+ ast_channel_publish_dial(qe->chan, peer, member->interface, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(peer)));
ast_autoservice_chan_hangup_peer(qe->chan, peer);
ao2_ref(member, -1);
return -1;
@@ -5855,10 +5804,10 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
ast_channel_unlock(qe->chan);
if (monitorfilename) {
ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1, X_REC_IN | X_REC_OUT);
- } else if (ast_channel_cdr(qe->chan)) {
- ast_monitor_start(which, qe->parent->monfmt, ast_channel_cdr(qe->chan)->uniqueid, 1, X_REC_IN | X_REC_OUT);
+ } else if (qe->chan) {
+ ast_monitor_start(which, qe->parent->monfmt, ast_channel_uniqueid(qe->chan), 1, X_REC_IN | X_REC_OUT);
} else {
- /* Last ditch effort -- no CDR, make up something */
+ /* Last ditch effort -- no channel, make up something */
snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
ast_monitor_start(which, qe->parent->monfmt, tmpid, 1, X_REC_IN | X_REC_OUT);
}
@@ -5871,8 +5820,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
if (mixmonapp) {
ast_debug(1, "Starting MixMonitor as requested.\n");
if (!monitorfilename) {
- if (ast_channel_cdr(qe->chan)) {
- ast_copy_string(tmpid, ast_channel_cdr(qe->chan)->uniqueid, sizeof(tmpid));
+ if (qe->chan) {
+ ast_copy_string(tmpid, ast_channel_uniqueid(qe->chan), sizeof(tmpid));
} else {
snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
}
@@ -5944,14 +5893,15 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
}
ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
- /* We purposely lock the CDR so that pbx_exec does not update the application data */
- if (ast_channel_cdr(qe->chan)) {
- ast_set_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED);
- }
+ /* BUGBUG
+ * This needs to be done differently. We need to start a MixMonitor on
+ * the actual queue bridge itself, not drop some channel out and pull it
+ * back. Once the media channel work is done, start a media channel on
+ * the bridge.
+ *
+ * Alternatively, don't use pbx_exec to put an audio hook on a channel.
+ */
pbx_exec(qe->chan, mixmonapp, mixmonargs);
- if (ast_channel_cdr(qe->chan)) {
- ast_clear_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED);
- }
} else {
ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
}
@@ -6039,33 +5989,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "CONNECT", "%ld|%s|%ld", (long) time(NULL) - qe->start, ast_channel_uniqueid(peer),
(long)(orig - to > 0 ? (orig - to) / 1000 : 0));
- if (ast_channel_cdr(qe->chan)) {
- struct ast_cdr *cdr;
- struct ast_cdr *newcdr;
-
- /* Only work with the last CDR in the stack*/
- cdr = ast_channel_cdr(qe->chan);
- while (cdr->next) {
- cdr = cdr->next;
- }
-
- /* If this CDR is not related to us add new one*/
- if ((strcasecmp(cdr->uniqueid, ast_channel_uniqueid(qe->chan))) &&
- (strcasecmp(cdr->linkedid, ast_channel_uniqueid(qe->chan))) &&
- (newcdr = ast_cdr_dup(cdr))) {
- ast_channel_lock(qe->chan);
- ast_cdr_init(newcdr, qe->chan);
- ast_cdr_reset(newcdr, 0);
- cdr = ast_cdr_append(cdr, newcdr);
- cdr = cdr->next;
- ast_channel_unlock(qe->chan);
- }
-
- if (update_cdr) {
- ast_copy_string(cdr->dstchannel, member->membername, sizeof(cdr->dstchannel));
- }
- }
-
blob = ast_json_pack("{s: s, s: s, s: s, s: i, s: i}",
"Queue", queuename,
"Interface", member->interface,
diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c
index 4bf3602cb..a590fb32a 100644
--- a/cdr/cdr_adaptive_odbc.c
+++ b/cdr/cdr_adaptive_odbc.c
@@ -433,7 +433,7 @@ static int odbc_log(struct ast_cdr *cdr)
ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm);
colptr = colbuf;
} else {
- ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0, datefield ? 0 : 1);
+ ast_cdr_format_var(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), datefield ? 0 : 1);
}
if (colptr) {
@@ -472,9 +472,9 @@ static int odbc_log(struct ast_cdr *cdr)
* form (but only when we're dealing with a character-based field).
*/
if (strcasecmp(entry->name, "disposition") == 0) {
- ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+ ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
} else if (strcasecmp(entry->name, "amaflags") == 0) {
- ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+ ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
}
/* Truncate too-long fields */
diff --git a/cdr/cdr_csv.c b/cdr/cdr_csv.c
index 5cfde82d7..a6f8a4dc0 100644
--- a/cdr/cdr_csv.c
+++ b/cdr/cdr_csv.c
@@ -234,7 +234,7 @@ static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
/* Disposition */
append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
/* AMA Flags */
- append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
+ append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize);
/* Unique ID */
if (loguniqueid)
append_string(buf, cdr->uniqueid, bufsize);
@@ -285,9 +285,6 @@ static int csv_log(struct ast_cdr *cdr)
char buf[1024];
char csvmaster[PATH_MAX];
snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
-#if 0
- printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);
-#endif
if (build_csv_record(buf, sizeof(buf), cdr)) {
ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
return 0;
diff --git a/cdr/cdr_custom.c b/cdr/cdr_custom.c
index 290e5344d..2a3b1a1dd 100644
--- a/cdr/cdr_custom.c
+++ b/cdr/cdr_custom.c
@@ -67,20 +67,20 @@ AST_THREADSTORAGE(custom_buf);
static const char name[] = "cdr-custom";
-struct cdr_config {
+struct cdr_custom_config {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(filename);
AST_STRING_FIELD(format);
);
ast_mutex_t lock;
- AST_RWLIST_ENTRY(cdr_config) list;
+ AST_RWLIST_ENTRY(cdr_custom_config) list;
};
-static AST_RWLIST_HEAD_STATIC(sinks, cdr_config);
+static AST_RWLIST_HEAD_STATIC(sinks, cdr_custom_config);
static void free_config(void)
{
- struct cdr_config *sink;
+ struct cdr_custom_config *sink;
while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
ast_mutex_destroy(&sink->lock);
ast_free(sink);
@@ -103,7 +103,7 @@ static int load_config(void)
var = ast_variable_browse(cfg, "mappings");
while (var) {
if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
- struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
+ struct cdr_custom_config *sink = ast_calloc_with_stringfields(1, struct cdr_custom_config, 1024);
if (!sink) {
ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
@@ -130,7 +130,7 @@ static int custom_log(struct ast_cdr *cdr)
{
struct ast_channel *dummy;
struct ast_str *str;
- struct cdr_config *config;
+ struct cdr_custom_config *config;
/* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */
if (!(str = ast_str_thread_get(&custom_buf, 16))) {
diff --git a/cdr/cdr_manager.c b/cdr/cdr_manager.c
index a82bcf989..e3ae7a57d 100644
--- a/cdr/cdr_manager.c
+++ b/cdr/cdr_manager.c
@@ -203,7 +203,7 @@ static int manager_log(struct ast_cdr *cdr)
cdr->accountcode, cdr->src, cdr->dst, cdr->dcontext, cdr->clid, cdr->channel,
cdr->dstchannel, cdr->lastapp, cdr->lastdata, strStartTime, strAnswerTime, strEndTime,
cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition),
- ast_cdr_flags2str(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf);
+ ast_channel_amaflags2string(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf);
return 0;
}
diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c
index 7ea2f041f..022d75210 100644
--- a/cdr/cdr_odbc.c
+++ b/cdr/cdr_odbc.c
@@ -124,10 +124,13 @@ static SQLHSTMT execute_cb(struct odbc_obj *obj, void *data)
SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL);
}
- if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING))
- SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL);
- else
+ if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING)) {
+ char *disposition;
+ disposition = ast_strdupa(ast_cdr_disp2str(cdr->disposition));
+ SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(disposition) + 1, 0, disposition, 0, NULL);
+ } else {
SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL);
+ }
SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL);
SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL);
diff --git a/cdr/cdr_pgsql.c b/cdr/cdr_pgsql.c
index 906f0227c..dc73de477 100644
--- a/cdr/cdr_pgsql.c
+++ b/cdr/cdr_pgsql.c
@@ -222,9 +222,9 @@ static int pgsql_log(struct ast_cdr *cdr)
AST_RWLIST_RDLOCK(&psql_columns);
AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
/* For fields not set, simply skip them */
- ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+ ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
if (strcmp(cur->name, "calldate") == 0 && !value) {
- ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
+ ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0);
}
if (!value) {
if (cur->notnull && !cur->hasdefault) {
@@ -286,7 +286,7 @@ static int pgsql_log(struct ast_cdr *cdr)
} else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
if (cur->type[0] == 'i') {
/* Get integer, no need to escape anything */
- ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+ ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
LENGTHEN_BUF2(13);
ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
} else if (strncmp(cur->type, "float", 5) == 0) {
@@ -302,18 +302,18 @@ static int pgsql_log(struct ast_cdr *cdr)
} else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
if (strncmp(cur->type, "int", 3) == 0) {
/* Integer, no need to escape anything */
- ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
+ ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1);
LENGTHEN_BUF2(13);
ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
} else {
/* Although this is a char field, there are no special characters in the values for these fields */
- ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+ ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
LENGTHEN_BUF2(31);
ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
}
} else {
/* Arbitrary field, could be anything */
- ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+ ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
if (strncmp(cur->type, "int", 3) == 0) {
long long whatever;
if (value && sscanf(value, "%30lld", &whatever) == 1) {
diff --git a/cdr/cdr_radius.c b/cdr/cdr_radius.c
index 92ec8a4b4..2bf2002fe 100644
--- a/cdr/cdr_radius.c
+++ b/cdr/cdr_radius.c
@@ -170,12 +170,12 @@ static int build_radius_record(VALUE_PAIR **tosend, struct ast_cdr *cdr)
return -1;
/* Disposition */
- tmp = ast_cdr_disp2str(cdr->disposition);
+ tmp = ast_strdupa(ast_cdr_disp2str(cdr->disposition));
if (!rc_avpair_add(rh, tosend, PW_AST_DISPOSITION, tmp, strlen(tmp), VENDOR_CODE))
return -1;
/* AMA Flags */
- tmp = ast_cdr_flags2str(cdr->amaflags);
+ tmp = ast_strdupa(ast_channel_amaflags2string(cdr->amaflags));
if (!rc_avpair_add(rh, tosend, PW_AST_AMA_FLAGS, tmp, strlen(tmp), VENDOR_CODE))
return -1;
diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c
index 8a7f07713..dec4d65e9 100644
--- a/cdr/cdr_syslog.c
+++ b/cdr/cdr_syslog.c
@@ -60,7 +60,7 @@ AST_THREADSTORAGE(syslog_buf);
static const char name[] = "cdr-syslog";
-struct cdr_config {
+struct cdr_syslog_config {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(ident);
AST_STRING_FIELD(format);
@@ -68,14 +68,14 @@ struct cdr_config {
int facility;
int priority;
ast_mutex_t lock;
- AST_LIST_ENTRY(cdr_config) list;
+ AST_LIST_ENTRY(cdr_syslog_config) list;
};
-static AST_RWLIST_HEAD_STATIC(sinks, cdr_config);
+static AST_RWLIST_HEAD_STATIC(sinks, cdr_syslog_config);
static void free_config(void)
{
- struct cdr_config *sink;
+ struct cdr_syslog_config *sink;
while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
ast_mutex_destroy(&sink->lock);
ast_free(sink);
@@ -86,7 +86,7 @@ static int syslog_log(struct ast_cdr *cdr)
{
struct ast_channel *dummy;
struct ast_str *str;
- struct cdr_config *sink;
+ struct cdr_syslog_config *sink;
/* Batching saves memory management here. Otherwise, it's the same as doing an
allocation and free each time. */
@@ -174,7 +174,7 @@ static int load_config(int reload)
}
while ((catg = ast_category_browse(cfg, catg))) {
- struct cdr_config *sink;
+ struct cdr_syslog_config *sink;
if (!strcasecmp(catg, "general")) {
continue;
@@ -186,7 +186,7 @@ static int load_config(int reload)
continue;
}
- sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
+ sink = ast_calloc_with_stringfields(1, struct cdr_syslog_config, 1024);
if (!sink) {
ast_log(AST_LOG_ERROR,
diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c
index dd75dbb46..aef57b55d 100644
--- a/cdr/cdr_tds.c
+++ b/cdr/cdr_tds.c
@@ -176,7 +176,7 @@ retry:
settings->table,
accountcode, src, dst, dcontext, clid, channel,
dstchannel, lastapp, lastdata, start, answer, end, hrduration,
- hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
+ hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
userfield
);
} else {
@@ -196,7 +196,7 @@ retry:
settings->table,
accountcode, src, dst, dcontext, clid, channel,
dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
- cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
+ cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
userfield
);
}
@@ -226,7 +226,7 @@ retry:
settings->table,
accountcode, src, dst, dcontext, clid, channel,
dstchannel, lastapp, lastdata, start, answer, end, hrduration,
- hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
+ hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
);
} else {
erc = dbfcmd(settings->dbproc,
@@ -245,7 +245,7 @@ retry:
settings->table,
accountcode, src, dst, dcontext, clid, channel,
dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
- cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
+ cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
);
}
}
diff --git a/cel/cel_manager.c b/cel/cel_manager.c
index e1d0dc148..245c7800d 100644
--- a/cel/cel_manager.c
+++ b/cel/cel_manager.c
@@ -129,7 +129,7 @@ static void manager_log(const struct ast_event *event, void *userdata)
record.application_name,
record.application_data,
start_time,
- ast_cel_get_ama_flag_name(record.amaflag),
+ ast_channel_amaflags2string(record.amaflag),
record.unique_id,
record.linked_id,
record.user_field,
diff --git a/cel/cel_radius.c b/cel/cel_radius.c
index 0edf57f4c..9067a0491 100644
--- a/cel/cel_radius.c
+++ b/cel/cel_radius.c
@@ -150,7 +150,7 @@ static int build_radius_record(VALUE_PAIR **send, struct ast_cel_event_record *r
return -1;
}
/* AMA Flags */
- amaflags = ast_strdupa(ast_cel_get_ama_flag_name(record->amaflag));
+ amaflags = ast_strdupa(ast_channel_amaflags2string(record->amaflag));
if (!rc_avpair_add(rh, send, PW_AST_AMA_FLAGS, amaflags, strlen(amaflags), VENDOR_CODE)) {
return -1;
}
diff --git a/cel/cel_tds.c b/cel/cel_tds.c
index df2b573bf..1bb4d517e 100644
--- a/cel/cel_tds.c
+++ b/cel/cel_tds.c
@@ -206,7 +206,7 @@ retry:
ciddnid_ai, exten_ai, context_ai, channel_ai, app_ai, appdata_ai, start,
(record.event_type == AST_CEL_USER_DEFINED)
? record.user_defined_name : record.event_name,
- ast_cel_get_ama_flag_name(record.amaflag), uniqueid_ai, linkedid_ai,
+ ast_channel_amaflags2string(record.amaflag), uniqueid_ai, linkedid_ai,
userfield_ai, peer_ai);
if (erc == FAIL) {
diff --git a/channels/chan_agent.c b/channels/chan_agent.c
index d72254ee7..57f0914cf 100644
--- a/channels/chan_agent.c
+++ b/channels/chan_agent.c
@@ -112,10 +112,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<option name="d">
<para>make the app return <literal>-1</literal> if there is an error condition.</para>
</option>
- <option name="c">
- <para>change the CDR so that the source of the call is
- <literal>Agent/agent_id</literal></para>
- </option>
<option name="n">
<para>don't generate the warnings when there is no callerid or the
agentid is not known. It's handy if you want to have one context
@@ -234,7 +230,6 @@ static char recordformat[AST_MAX_BUF] = "";
static char recordformatext[AST_MAX_BUF] = "";
static char urlprefix[AST_MAX_BUF] = "";
static char savecallsin[AST_MAX_BUF] = "";
-static int updatecdr = 0;
static char beep[AST_MAX_BUF] = "beep";
#define GETAGENTBYCALLERID "AGENTBYCALLERID"
@@ -573,7 +568,7 @@ static int agent_answer(struct ast_channel *ast)
static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock)
{
- char tmp[AST_MAX_BUF],tmp2[AST_MAX_BUF], *pointer;
+ char tmp[AST_MAX_BUF], tmp2[AST_MAX_BUF], *pointer;
char filename[AST_MAX_BUF];
int res = -1;
if (!p)
@@ -590,9 +585,7 @@ static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p
#if 0
ast_verbose("name is %s, link is %s\n",tmp, tmp2);
#endif
- if (!ast_channel_cdr(ast))
- ast_channel_cdr_set(ast, ast_cdr_alloc());
- ast_cdr_setuserfield(ast, tmp2);
+ ast_cdr_setuserfield(ast_channel_name(ast), tmp2);
res = 0;
} else
ast_log(LOG_ERROR, "Recording already started on that call.\n");
@@ -1199,11 +1192,6 @@ static int read_agent_config(int reload)
strcpy(agentgoodbye,v->value);
} else if (!strcasecmp(v->name, "musiconhold")) {
ast_copy_string(moh, v->value, sizeof(moh));
- } else if (!strcasecmp(v->name, "updatecdr")) {
- if (ast_true(v->value))
- updatecdr = 1;
- else
- updatecdr = 0;
} else if (!strcasecmp(v->name, "autologoffunavail")) {
if (ast_true(v->value))
autologoffunavail = 1;
@@ -1898,7 +1886,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
const char *tmpoptions = NULL;
int play_announcement = 1;
char agent_goodbye[AST_MAX_FILENAME_LEN];
- int update_cdr = updatecdr;
user[0] = '\0';
xpass[0] = '\0';
@@ -1918,14 +1905,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES");
ast_verb(3, "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,ast_channel_name(chan));
}
- if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"))) {
- if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR")))
- update_cdr = 1;
- else
- update_cdr = 0;
- tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR");
- ast_verb(3, "Saw variable AGENTUPDATECDR=%s, setting update_cdr to: %d on Channel '%s'.\n",tmpoptions,update_cdr,ast_channel_name(chan));
- }
if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"))) {
strcpy(agent_goodbye, pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"));
tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTGOODBYE");
@@ -2093,8 +2072,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
"Channel: %s\r\n"
"Uniqueid: %s\r\n",
p->agent, ast_channel_name(chan), ast_channel_uniqueid(chan));
- if (update_cdr && ast_channel_cdr(chan))
- snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "%s", agent);
ast_queue_log("NONE", ast_channel_uniqueid(chan), agent, "AGENTLOGIN", "%s", ast_channel_name(chan));
ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", p->agent,
ast_getformatname(ast_channel_readformat(chan)), ast_getformatname(ast_channel_writeformat(chan)));
@@ -2242,17 +2219,16 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data)
{
int exitifnoagentid = 0;
int nowarnings = 0;
- int changeoutgoing = 0;
int res = 0;
char agent[AST_MAX_AGENT];
if (data) {
- if (strchr(data, 'd'))
+ if (strchr(data, 'd')) {
exitifnoagentid = 1;
- if (strchr(data, 'n'))
+ }
+ if (strchr(data, 'n')) {
nowarnings = 1;
- if (strchr(data, 'c'))
- changeoutgoing = 1;
+ }
}
if (ast_channel_caller(chan)->id.number.valid
&& !ast_strlen_zero(ast_channel_caller(chan)->id.number.str)) {
@@ -2266,7 +2242,6 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data)
AST_LIST_LOCK(&agents);
AST_LIST_TRAVERSE(&agents, p, list) {
if (!strcasecmp(p->agent, tmp)) {
- if (changeoutgoing) snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "Agent/%s", p->agent);
__agent_start_monitoring(chan, p, 1);
break;
}
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 109fac06d..24337e385 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -107,7 +107,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/callerid.h"
#include "asterisk/adsi.h"
#include "asterisk/cli.h"
-#include "asterisk/cdr.h"
#include "asterisk/cel.h"
#include "asterisk/features.h"
#include "asterisk/musiconhold.h"
@@ -17726,7 +17725,7 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
} else if (!strcasecmp(v->name, "accountcode")) {
ast_copy_string(confp->chan.accountcode, v->value, sizeof(confp->chan.accountcode));
} else if (!strcasecmp(v->name, "amaflags")) {
- y = ast_cdr_amaflags2int(v->value);
+ y = ast_channel_string2amaflag(v->value);
if (y < 0)
ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d.\n", v->value, v->lineno);
else
diff --git a/channels/chan_h323.c b/channels/chan_h323.c
index 61817dbff..e26bb5fb6 100644
--- a/channels/chan_h323.c
+++ b/channels/chan_h323.c
@@ -1482,7 +1482,7 @@ static struct oh323_user *build_user(const char *name, struct ast_variable *v, s
/* Let us know we need to use ip authentication */
user->host = 1;
} else if (!strcasecmp(v->name, "amaflags")) {
- format = ast_cdr_amaflags2int(v->value);
+ format = ast_channel_string2amaflag(v->value);
if (format < 0) {
ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
} else {
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 7c0de9936..486af52a7 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -77,7 +77,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/translate.h"
#include "asterisk/md5.h"
-#include "asterisk/cdr.h"
#include "asterisk/crypto.h"
#include "asterisk/acl.h"
#include "asterisk/manager.h"
@@ -12802,7 +12801,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
} else if (!strcasecmp(v->name, "language")) {
ast_string_field_set(user, language, v->value);
} else if (!strcasecmp(v->name, "amaflags")) {
- format = ast_cdr_amaflags2int(v->value);
+ format = ast_channel_string2amaflag(v->value);
if (format < 0) {
ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
} else {
@@ -13273,7 +13272,7 @@ static int set_config(const char *config_file, int reload, int forced)
} else if (!strcasecmp(v->name, "mohsuggest")) {
ast_copy_string(mohsuggest, v->value, sizeof(mohsuggest));
} else if (!strcasecmp(v->name, "amaflags")) {
- format = ast_cdr_amaflags2int(v->value);
+ format = ast_channel_string2amaflag(v->value);
if (format < 0) {
ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
} else {
@@ -14513,7 +14512,7 @@ static int users_data_provider_get(const struct ast_data_search *search,
continue;
}
ast_data_add_int(data_enum_node, "value", user->amaflags);
- ast_data_add_str(data_enum_node, "text", ast_cdr_flags2str(user->amaflags));
+ ast_data_add_str(data_enum_node, "text", ast_channel_amaflags2string(user->amaflags));
ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1);
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index 9080dbaf0..5a0b7ad84 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/callerid.h"
#include "asterisk/cli.h"
#include "asterisk/say.h"
-#include "asterisk/cdr.h"
#include "asterisk/astdb.h"
#include "asterisk/features.h"
#include "asterisk/app.h"
@@ -4087,7 +4086,7 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v)
} else if (!strcasecmp(v->name, "accountcode")) {
ast_copy_string(accountcode, v->value, sizeof(accountcode));
} else if (!strcasecmp(v->name, "amaflags")) {
- y = ast_cdr_amaflags2int(v->value);
+ y = ast_channel_string2amaflag(v->value);
if (y < 0) {
ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
} else {
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index eb79d237e..fbd7f1c22 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -20224,7 +20224,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
ast_cli(fd, " Tonezone : %s\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>");
if (!ast_strlen_zero(peer->accountcode))
ast_cli(fd, " Accountcode : %s\n", peer->accountcode);
- ast_cli(fd, " AMA flags : %s\n", ast_cdr_flags2str(peer->amaflags));
+ ast_cli(fd, " AMA flags : %s\n", ast_channel_amaflags2string(peer->amaflags));
ast_cli(fd, " Transfer mode: %s\n", transfermode2str(peer->allowtransfer));
ast_cli(fd, " CallingPres : %s\n", ast_describe_caller_presentation(peer->callingpres));
if (!ast_strlen_zero(peer->fromuser))
@@ -20362,7 +20362,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
astman_append(s, "ToneZone: %s\r\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>");
if (!ast_strlen_zero(peer->accountcode))
astman_append(s, "Accountcode: %s\r\n", peer->accountcode);
- astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(peer->amaflags));
+ astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(peer->amaflags));
astman_append(s, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres));
if (!ast_strlen_zero(peer->fromuser))
astman_append(s, "SIP-FromUser: %s\r\n", peer->fromuser);
@@ -20537,7 +20537,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args
ast_cli(a->fd, " Language : %s\n", user->language);
if (!ast_strlen_zero(user->accountcode))
ast_cli(a->fd, " Accountcode : %s\n", user->accountcode);
- ast_cli(a->fd, " AMA flags : %s\n", ast_cdr_flags2str(user->amaflags));
+ ast_cli(a->fd, " AMA flags : %s\n", ast_channel_amaflags2string(user->amaflags));
ast_cli(a->fd, " Tonezone : %s\n", user->zone[0] != '\0' ? user->zone : "<Not set>");
ast_cli(a->fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer));
ast_cli(a->fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate);
@@ -20724,8 +20724,6 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags)
struct sip_pvt *cur = __cur;
struct ast_rtp_instance_stats stats;
char durbuf[10];
- int duration;
- int durh, durm, durs;
struct ast_channel *c;
struct __show_chan_arg *arg = __arg;
int fd = arg->fd;
@@ -20756,12 +20754,8 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags)
return 0;
}
- if (c && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
- duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
- durh = duration / 3600;
- durm = (duration % 3600) / 60;
- durs = duration % 60;
- snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
+ if (c) {
+ ast_format_duration_hh_mm_ss(ast_channel_get_duration(c), durbuf, sizeof(durbuf));
} else {
durbuf[0] = '\0';
}
@@ -21694,11 +21688,8 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req)
} else if (!ast_strlen_zero(c = sip_get_header(req, "X-ClientCode"))) {
/* Client code (from SNOM phone) */
if (ast_test_flag(&p->flags[0], SIP_USECLIENTCODE)) {
- if (p->owner && ast_channel_cdr(p->owner)) {
- ast_cdr_setuserfield(p->owner, c);
- }
- if (p->owner && ast_bridged_channel(p->owner) && ast_channel_cdr(ast_bridged_channel(p->owner))) {
- ast_cdr_setuserfield(ast_bridged_channel(p->owner), c);
+ if (p->owner) {
+ ast_cdr_setuserfield(ast_channel_name(p->owner), c);
}
transmit_response(p, "200 OK", req);
} else {
@@ -24831,7 +24822,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req,
ast_channel_unlock(c);
sip_pvt_unlock(p);
- ast_raw_answer(c, 1);
+ ast_raw_answer(c);
ast_channel_lock(replaces_chan);
bridge = ast_channel_get_bridge(replaces_chan);
@@ -30458,7 +30449,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
} else if (!strcasecmp(v->name, "callbackextension")) {
ast_string_field_set(peer, callback, v->value);
} else if (!strcasecmp(v->name, "amaflags")) {
- format = ast_cdr_amaflags2int(v->value);
+ format = ast_channel_string2amaflag(v->value);
if (format < 0) {
ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", v->value, v->lineno);
} else {
@@ -34023,7 +34014,7 @@ static int peers_data_provider_get(const struct ast_data_search *search,
continue;
}
ast_data_add_int(enum_node, "value", peer->amaflags);
- ast_data_add_str(enum_node, "text", ast_cdr_flags2str(peer->amaflags));
+ ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(peer->amaflags));
/* sip options */
data_sip_options = ast_data_add_node(data_peer, "sipoptions");
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 5e0756654..ad51edf10 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/manager.h"
#include "asterisk/say.h"
-#include "asterisk/cdr.h"
#include "asterisk/astdb.h"
#include "asterisk/features.h"
#include "asterisk/app.h"
@@ -4477,7 +4476,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
ast_str_reset(tmp_str);
ast_cli(fd, "Language: %s\n", S_OR(l->language, "<not set>"));
ast_cli(fd, "Accountcode: %s\n", S_OR(l->accountcode, "<not set>"));
- ast_cli(fd, "AmaFlag: %s\n", ast_cdr_flags2str(l->amaflags));
+ ast_cli(fd, "AmaFlag: %s\n", ast_channel_amaflags2string(l->amaflags));
ast_cli(fd, "CallerId Number: %s\n", S_OR(l->cid_num, "<not set>"));
ast_cli(fd, "CallerId Name: %s\n", S_OR(l->cid_name, "<not set>"));
ast_cli(fd, "Hide CallerId: %s\n", (l->hidecallerid ? "Yes" : "No"));
@@ -4535,7 +4534,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
ast_str_reset(tmp_str);
astman_append(s, "Language: %s\r\n", S_OR(l->language, "<not set>"));
astman_append(s, "Accountcode: %s\r\n", S_OR(l->accountcode, "<not set>"));
- astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(l->amaflags));
+ astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(l->amaflags));
astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), l->cid_name, l->cid_num, ""));
astman_append(s, "HideCallerId: %s\r\n", (l->hidecallerid ? "Yes" : "No"));
astman_append(s, "CFwdAll: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "<not set>"));
@@ -7809,7 +7808,7 @@ static void config_parse_variables(int type, void *item, struct ast_variable *vp
}
} else if (!strcasecmp(v->name, "amaflags")) {
if (type & (TYPE_DEF_LINE | TYPE_LINE)) {
- int tempamaflags = ast_cdr_amaflags2int(v->value);
+ int tempamaflags = ast_channel_string2amaflag(v->value);
if (tempamaflags < 0) {
ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
} else {
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index fbc6adda2..f512b0f6f 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -6392,7 +6392,7 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var
ast_copy_string(lt->accountcode, v->value, sizeof(lt->accountcode));
} else if (!strcasecmp(v->name, "amaflags")) {
int y;
- y = ast_cdr_amaflags2int(v->value);
+ y = ast_channel_string2amaflag(v->value);
if (y < 0) {
ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value,
v->lineno);
diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c
index 30026af3d..b4649c177 100644
--- a/funcs/func_callerid.c
+++ b/funcs/func_callerid.c
@@ -1142,9 +1142,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
ast_channel_redirecting(chan)->from.number.valid = 1;
ast_free(ast_channel_redirecting(chan)->from.number.str);
ast_channel_redirecting(chan)->from.number.str = ast_strdup(value);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
} else if (!strcasecmp("dnid", member.argv[0])) {
ast_party_dialed_set_init(&dialed, ast_channel_dialed(chan));
if (member.argc == 1) {
@@ -1162,9 +1159,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
dialed.number.str = ast_strdup(value);
ast_trim_blanks(dialed.number.str);
ast_party_dialed_set(ast_channel_dialed(chan), &dialed);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
} else if (member.argc == 3 && !strcasecmp("plan", member.argv[2])) {
/* dnid-num-plan */
val = ast_strdupa(value);
@@ -1172,9 +1166,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
if (('0' <= val[0]) && (val[0] <= '9')) {
ast_channel_dialed(chan)->number.plan = atoi(val);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
} else {
ast_log(LOG_ERROR,
"Unknown type-of-number/numbering-plan '%s', value unchanged\n", val);
@@ -1192,9 +1183,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
switch (status) {
case ID_FIELD_VALID:
ast_party_dialed_set(ast_channel_dialed(chan), &dialed);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
break;
case ID_FIELD_INVALID:
break;
@@ -1212,9 +1200,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
if (('0' <= val[0]) && (val[0] <= '9')) {
ast_channel_caller(chan)->ani2 = atoi(val);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
} else {
ast_log(LOG_ERROR, "Unknown callerid ani2 '%s', value unchanged\n", val);
}
@@ -1229,9 +1214,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
switch (status) {
case ID_FIELD_VALID:
ast_party_caller_set(ast_channel_caller(chan), &caller, NULL);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
break;
case ID_FIELD_INVALID:
break;
@@ -1246,9 +1228,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
switch (status) {
case ID_FIELD_VALID:
ast_party_caller_set(ast_channel_caller(chan), &caller, NULL);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
break;
case ID_FIELD_INVALID:
break;
@@ -1263,9 +1242,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
switch (status) {
case ID_FIELD_VALID:
ast_channel_set_caller_event(chan, &caller, NULL);
- if (ast_channel_cdr(chan)) {
- ast_cdr_setcid(ast_channel_cdr(chan), chan);
- }
break;
case ID_FIELD_INVALID:
break;
diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c
index adb42742b..0f900feda 100644
--- a/funcs/func_cdr.c
+++ b/funcs/func_cdr.c
@@ -56,7 +56,27 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Last application arguments.</para>
</enum>
<enum name="disposition">
- <para>ANSWERED, NO ANSWER, BUSY, FAILED.</para>
+ <para>The final state of the CDR.</para>
+ <enumlist>
+ <enum name="0">
+ <para><literal>NO ANSWER</literal></para>
+ </enum>
+ <enum name="1">
+ <para><literal>NO ANSWER</literal> (NULL record)</para>
+ </enum>
+ <enum name="2">
+ <para><literal>FAILED</literal></para>
+ </enum>
+ <enum name="4">
+ <para><literal>BUSY</literal></para>
+ </enum>
+ <enum name="8">
+ <para><literal>ANSWERED</literal></para>
+ </enum>
+ <enum name="16">
+ <para><literal>CONGESTION</literal></para>
+ </enum>
+ </enumlist>
</enum>
<enum name="src">
<para>Source.</para>
@@ -65,7 +85,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Time the call started.</para>
</enum>
<enum name="amaflags">
- <para>DOCUMENTATION, BILL, IGNORE, etc.</para>
+ <para>R/W the Automatic Message Accounting (AMA) flags on the channel.
+ When read from a channel, the integer value will always be returned.
+ When written to a channel, both the string format or integer value
+ is accepted.</para>
+ <enumlist>
+ <enum name="1"><para><literal>OMIT</literal></para></enum>
+ <enum name="2"><para><literal>BILLING</literal></para></enum>
+ <enum name="3"><para><literal>DOCUMENTATION</literal></para></enum>
+ </enumlist>
+ <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
</enum>
<enum name="dst">
<para>Destination.</para>
@@ -75,6 +104,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</enum>
<enum name="accountcode">
<para>The channel's account code.</para>
+ <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
</enum>
<enum name="dcontext">
<para>Destination context.</para>
@@ -113,16 +143,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<option name="f">
<para>Returns billsec or duration fields as floating point values.</para>
</option>
- <option name="l">
- <para>Uses the most recent CDR on a channel with multiple records</para>
- </option>
- <option name="r">
- <para>Searches the entire stack of CDRs on the channel.</para>
- </option>
- <option name="s">
- <para>Skips any CDR's that are marked 'LOCKED' due to forkCDR() calls.
- (on setting/writing CDR vars only)</para>
- </option>
<option name="u">
<para>Retrieves the raw, unprocessed value.</para>
<para>For example, 'start', 'answer', and 'end' will be retrieved as epoch
@@ -137,138 +157,132 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>All of the CDR field names are read-only, except for <literal>accountcode</literal>,
<literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply
a name not on the above list, and create your own variable, whose value can be changed
- with this function, and this variable will be stored on the cdr.</para>
- <note><para>For setting CDR values, the <literal>l</literal> flag does not apply to
- setting the <literal>accountcode</literal>, <literal>userfield</literal>, or
- <literal>amaflags</literal>.</para><para>CDRs can only be modified before the bridge
- between two channels is torn down. For example, CDRs may not be modified after the
- <literal>Dial</literal> application has returned.</para></note>
- <para>Raw values for <literal>disposition</literal>:</para>
- <enumlist>
- <enum name="0">
- <para>NO ANSWER</para>
- </enum>
- <enum name="1">
- <para>NO ANSWER (NULL record)</para>
- </enum>
- <enum name="2">
- <para>FAILED</para>
- </enum>
- <enum name="4">
- <para>BUSY</para>
- </enum>
- <enum name="8">
- <para>ANSWERED</para>
- </enum>
- </enumlist>
- <para>Raw values for <literal>amaflags</literal>:</para>
- <enumlist>
- <enum name="1">
- <para>OMIT</para>
- </enum>
- <enum name="2">
- <para>BILLING</para>
- </enum>
- <enum name="3">
- <para>DOCUMENTATION</para>
- </enum>
- </enumlist>
+ with this function, and this variable will be stored on the CDR.</para>
+ <note><para>CDRs can only be modified before the bridge between two channels is
+ torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
+ application has returned.</para></note>
<para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
</description>
</function>
+ <function name="CDR_PROP" language="en_US">
+ <synopsis>
+ Set a property on a channel's CDR.
+ </synopsis>
+ <syntax>
+ <parameter name="name" required="true">
+ <para>The property to set on the CDR.</para>
+ <enumlist>
+ <enum name="party_a">
+ <para>Set this channel as the preferred Party A when
+ channels are associated together.</para>
+ <para>Write-Only</para>
+ </enum>
+ <enum name="disable">
+ <para>Disable CDRs for this channel.</para>
+ <para>Write-Only</para>
+ </enum>
+ </enumlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>This function sets a property on a channel's CDR. Properties
+ alter the behavior of how the CDR operates for that channel.</para>
+ </description>
+ </function>
***/
enum cdr_option_flags {
- OPT_RECURSIVE = (1 << 0),
OPT_UNPARSED = (1 << 1),
- OPT_LAST = (1 << 2),
- OPT_SKIPLOCKED = (1 << 3),
- OPT_FLOAT = (1 << 4),
+ OPT_FLOAT = (1 << 2),
};
AST_APP_OPTIONS(cdr_func_options, {
AST_APP_OPTION('f', OPT_FLOAT),
- AST_APP_OPTION('l', OPT_LAST),
- AST_APP_OPTION('r', OPT_RECURSIVE),
- AST_APP_OPTION('s', OPT_SKIPLOCKED),
AST_APP_OPTION('u', OPT_UNPARSED),
});
static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
char *buf, size_t len)
{
- char *ret = NULL;
+ char format_buf[128];
struct ast_flags flags = { 0 };
- struct ast_cdr *cdr;
+ char tempbuf[128];
+ char *info;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(variable);
AST_APP_ARG(options);
);
- if (ast_strlen_zero(parse) || !chan)
+ if (!chan) {
return -1;
+ }
- ast_channel_lock(chan);
- cdr = ast_channel_cdr(chan);
- if (!cdr) {
- ast_channel_unlock(chan);
+ if (ast_strlen_zero(parse)) {
+ ast_log(AST_LOG_WARNING, "FUNC_CDR requires a variable (FUNC_CDR(variable[,option]))\n)");
return -1;
}
+ info = ast_strdupa(parse);
+ AST_STANDARD_APP_ARGS(args, info);
- AST_STANDARD_APP_ARGS(args, parse);
-
- if (!ast_strlen_zero(args.options))
+ if (!ast_strlen_zero(args.options)) {
ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
+ }
- if (ast_test_flag(&flags, OPT_LAST))
- while (cdr->next)
- cdr = cdr->next;
-
- if (ast_test_flag(&flags, OPT_SKIPLOCKED))
- while (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED) && cdr->next)
- cdr = cdr->next;
-
- if (!strcasecmp("billsec", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) {
- if (!ast_tvzero(cdr->answer)) {
- double hrtime;
-
- if(!ast_tvzero(cdr->end))
- hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
- else
- hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->answer) / 1000000.0);
+ if (ast_cdr_getvar(ast_channel_name(chan), args.variable, tempbuf, sizeof(tempbuf))) {
+ return 0;
+ }
- snprintf(buf, len, "%lf", hrtime);
- } else {
- snprintf(buf, len, "%lf", 0.0);
+ if (ast_test_flag(&flags, OPT_FLOAT) && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
+ long ms;
+ double dtime;
+ if (sscanf(tempbuf, "%30ld", &ms) != 1) {
+ ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+ args.variable, tempbuf, ast_channel_name(chan));
+ return 0;
}
- ret = buf;
- } else if (!strcasecmp("duration", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) {
- double hrtime;
-
- if(!ast_tvzero(cdr->end))
- hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
- else
- hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->start) / 1000000.0);
-
- snprintf(buf, len, "%lf", hrtime);
-
- if (!ast_strlen_zero(buf)) {
- ret = buf;
+ dtime = (double)(ms / 1000.0);
+ sprintf(tempbuf, "%lf", dtime);
+ } else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
+ if (!strcasecmp("start", args.variable)
+ || !strcasecmp("end", args.variable)
+ || !strcasecmp("answer", args.variable)) {
+ struct timeval fmt_time;
+ struct ast_tm tm;
+ if (sscanf(tempbuf, "%ld.%ld", &fmt_time.tv_sec, &fmt_time.tv_usec) != 2) {
+ ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+ args.variable, tempbuf, ast_channel_name(chan));
+ return 0;
+ }
+ ast_localtime(&fmt_time, &tm, NULL);
+ ast_strftime(tempbuf, sizeof(*tempbuf), "%Y-%m-%d %T", &tm);
+ } else if (!strcasecmp("disposition", args.variable)) {
+ int disposition;
+ if (sscanf(tempbuf, "%8d", &disposition) != 1) {
+ ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+ args.variable, tempbuf, ast_channel_name(chan));
+ return 0;
+ }
+ sprintf(format_buf, "%s", ast_cdr_disp2str(disposition));
+ strcpy(tempbuf, format_buf);
+ } else if (!strcasecmp("amaflags", args.variable)) {
+ int amaflags;
+ if (sscanf(tempbuf, "%8d", &amaflags) != 1) {
+ ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+ args.variable, tempbuf, ast_channel_name(chan));
+ return 0;
+ }
+ sprintf(format_buf, "%s", ast_channel_amaflags2string(amaflags));
+ strcpy(tempbuf, format_buf);
}
- } else {
- ast_cdr_getvar(cdr, args.variable, &ret, buf, len,
- ast_test_flag(&flags, OPT_RECURSIVE),
- ast_test_flag(&flags, OPT_UNPARSED));
}
- ast_channel_unlock(chan);
- return ret ? 0 : -1;
+ ast_copy_string(buf, tempbuf, len);
+ return 0;
}
static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
const char *value)
{
- struct ast_cdr *cdr;
struct ast_flags flags = { 0 };
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(variable);
@@ -278,36 +292,59 @@ static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
if (ast_strlen_zero(parse) || !value || !chan)
return -1;
- ast_channel_lock(chan);
- cdr = ast_channel_cdr(chan);
- if (!cdr) {
- ast_channel_unlock(chan);
- return -1;
- }
-
AST_STANDARD_APP_ARGS(args, parse);
if (!ast_strlen_zero(args.options))
ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
- if (ast_test_flag(&flags, OPT_LAST))
- while (cdr->next)
- cdr = cdr->next;
-
- if (!strcasecmp(args.variable, "accountcode")) /* the 'l' flag doesn't apply to setting the accountcode, userfield, or amaflags */
- ast_cdr_setaccount(chan, value);
- else if (!strcasecmp(args.variable, "peeraccount"))
- ast_cdr_setpeeraccount(chan, value);
- else if (!strcasecmp(args.variable, "userfield"))
- ast_cdr_setuserfield(chan, value);
- else if (!strcasecmp(args.variable, "amaflags"))
- ast_cdr_setamaflags(chan, value);
- else
- ast_cdr_setvar(cdr, args.variable, value, ast_test_flag(&flags, OPT_RECURSIVE));
- /* No need to worry about the u flag, as all fields for which setting
- * 'u' would do anything are marked as readonly. */
-
- ast_channel_unlock(chan);
+ if (!strcasecmp(args.variable, "accountcode")) {
+ ast_log(AST_LOG_WARNING, "Using the CDR function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n");
+ ast_channel_lock(chan);
+ ast_channel_accountcode_set(chan, value);
+ ast_channel_unlock(chan);
+ } else if (!strcasecmp(args.variable, "peeraccount")) {
+ ast_log(AST_LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
+ } else if (!strcasecmp(args.variable, "userfield")) {
+ ast_cdr_setuserfield(ast_channel_name(chan), value);
+ } else if (!strcasecmp(args.variable, "amaflags")) {
+ ast_log(AST_LOG_WARNING, "Using the CDR function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n");
+ if (isdigit(*value)) {
+ int amaflags;
+ sscanf(value, "%30d", &amaflags);
+ ast_channel_lock(chan);
+ ast_channel_amaflags_set(chan, amaflags);
+ ast_channel_unlock(chan);
+ } else {
+ ast_channel_lock(chan);
+ ast_channel_amaflags_set(chan, ast_channel_string2amaflag(value));
+ ast_channel_unlock(chan);
+ }
+ } else {
+ ast_cdr_setvar(ast_channel_name(chan), args.variable, value);
+ }
+
+ return 0;
+}
+
+static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse,
+ const char *value)
+{
+ enum ast_cdr_options option;
+
+ if (!strcasecmp("party_a", cmd)) {
+ option = AST_CDR_FLAG_PARTY_A;
+ } else if (!strcasecmp("disable", cmd)) {
+ option = AST_CDR_FLAG_DISABLE_ALL;
+ } else {
+ ast_log(AST_LOG_WARNING, "Unknown option %s used with CDR_PROP\n", cmd);
+ return 0;
+ }
+
+ if (ast_true(value)) {
+ ast_cdr_set_property(ast_channel_name(chan), option);
+ } else {
+ ast_cdr_clear_property(ast_channel_name(chan), option);
+ }
return 0;
}
@@ -317,14 +354,30 @@ static struct ast_custom_function cdr_function = {
.write = cdr_write,
};
+static struct ast_custom_function cdr_prop_function = {
+ .name = "CDR_PROP",
+ .read = NULL,
+ .write = cdr_prop_write,
+};
+
static int unload_module(void)
{
- return ast_custom_function_unregister(&cdr_function);
+ int res = 0;
+
+ res |= ast_custom_function_unregister(&cdr_function);
+ res |= ast_custom_function_unregister(&cdr_prop_function);
+
+ return res;
}
static int load_module(void)
{
- return ast_custom_function_register(&cdr_function);
+ int res = 0;
+
+ res |= ast_custom_function_register(&cdr_function);
+ res |= ast_custom_function_register(&cdr_prop_function);
+
+ return res;
}
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan function");
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan functions");
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 937924400..4327fd257 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -343,13 +343,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</function>
***/
-/*
- * BUGBUG add CHANNEL(after_bridge_goto)=<parseable-goto> Sets an after bridge goto datastore property on the channel.
- * CHANNEL(after_bridge_goto)=<empty> Deletes any after bridge goto datastore property on the channel.
- *
- * BUGBUG add CHANNEL(dtmf_features)=tkhwx sets channel dtmf features to specified. (transfer, park, hangup, monitor, mixmonitor)
- */
-
#define locked_copy_string(chan, dest, source, len) \
do { \
ast_channel_lock(chan); \
@@ -450,7 +443,7 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
ast_channel_lock(chan);
p = ast_bridged_channel(chan);
- if (p || ast_channel_tech(chan) || ast_channel_cdr(chan)) /* dummy channel? if so, we hid the peer name in the language */
+ if (p || ast_channel_tech(chan)) /* dummy channel? if so, we hid the peer name in the language */
ast_copy_string(buf, (p ? ast_channel_name(p) : ""), len);
else {
/* a dummy channel can still pass along bridged peer info via
@@ -525,7 +518,7 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
locked_string_field_set(chan, userfield, value);
else if (!strcasecmp(data, "amaflags")) {
ast_channel_lock(chan);
- if(isdigit(*value)) {
+ if (isdigit(*value)) {
int amaflags;
sscanf(value, "%30d", &amaflags);
ast_channel_amaflags_set(chan, amaflags);
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index 9d3f5b3cf..0bfcc3254 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -1032,6 +1032,37 @@ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast
int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action);
/*!
+ * \brief Update the linkedid for all channels in a bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge to update the linkedids on
+ * \param bridge_channel The channel joining the bridge
+ * \param swap The channel being swapped out of the bridge. May be NULL.
+ *
+ * \note The bridge must be locked prior to calling this function.
+ * \note This API call is meant for internal bridging operations.
+ */
+void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Update the accountcodes for a channel entering a bridge
+ * \since 12.0.0
+ *
+ * This function updates the accountcode and peeraccount on channels in two-party
+ * bridges. In multi-party bridges, peeraccount is not set - it doesn't make much sense -
+ * however accountcode propagation will still occur if the channel joining has an
+ * accountcode.
+ *
+ * \param bridge The bridge to update the accountcodes in
+ * \param bridge_channel The channel joining the bridge
+ * \param swap The channel being swapped out of the bridge. May be NULL.
+ *
+ * \note The bridge must be locked prior to calling this function.
+ * \note This API call is meant for internal bridging operations.
+ */
+void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
+/*!
* \brief Write a frame to the specified bridge_channel.
* \since 12.0.0
*
diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h
index c2268e312..548ca994d 100644
--- a/include/asterisk/cdr.h
+++ b/include/asterisk/cdr.h
@@ -26,33 +26,216 @@
#ifndef _ASTERISK_CDR_H
#define _ASTERISK_CDR_H
-#include <sys/time.h>
+#include "asterisk/channel.h"
+
+/*! \file
+ *
+ * \since 12
+ *
+ * \brief Call Detail Record Engine.
+ *
+ * \page CDR Call Detail Record Engine
+ *
+ * \par Intro
+ *
+ * The Call Detail Record (CDR) engine uses the \ref stasis Stasis Message Bus
+ * to build records for the channels in Asterisk. As the state of a channel and
+ * the bridges it participates in changes, notifications are sent over the
+ * Stasis Message Bus. The CDR engine consumes these notifications and builds
+ * records that reflect that state. Over the lifetime of a channel, many CDRs
+ * may be generated for that channel or that involve that channel.
+ *
+ * CDRs have a lifecycle that is a subset of the channel that they reflect. A
+ * single CDR for a channel represents a path of communication between the
+ * endpoint behind a channel and Asterisk, or between two endpoints. When a
+ * channel establishes a new path of communication, a new CDR is created for the
+ * channel. Likewise, when a path of communication is terminated, a CDR is
+ * finalized. Finally, when a channel is no longer present in Asterisk, all CDRs
+ * for that channel are dispatched for recording.
+ *
+ * Dispatching of CDRs occurs to registered CDR backends. CDR backends register
+ * through \ref ast_cdr_register and are responsible for taking the produced
+ * CDRs and storing them in permanent storage.
+ *
+ * \par CDR attributes
+ *
+ * While a CDR can have many attributes, all CDRs have two parties: a Party A
+ * and a Party B. The Party A is \em always the channel that owns the CDR. A CDR
+ * may or may not have a Party B, depending on its state.
+ *
+ * For the most part, attributes on a CDR are reflective of those same
+ * attributes on the channel at the time when the CDR was finalized. Specific
+ * CDR attributes include:
+ * \li \c start The time when the CDR was created
+ * \li \c answer The time when the Party A was answered, or when the path of
+ * communication between Party A and Party B was established
+ * \li \c end The time when the CDR was finalized
+ * \li \c duration \c end - \c start. If \c end is not known, the current time
+ * is used
+ * \li \c billsec \c end - \c answer. If \c end is not known, the current time
+ * is used
+ * \li \c userfield User set data on some party in the CDR
+ *
+ * Note that \c accountcode and \c amaflags are actually properties of a
+ * channel, not the CDR.
+ *
+ * \par CDR States
+ *
+ * CDRs go through various states during their lifetime. State transitions occur
+ * due to messages received over the \ref stasis Stasis Message Bus. The
+ * following describes the possible states a CDR can be in, and how it
+ * transitions through the states.
+ *
+ * \par Single
+ *
+ * When a CDR is created, it is put into the Single state. The Single state
+ * represents a CDR for a channel that has no Party B. CDRs can be unanswered
+ * or answered while in the Single state.
+ *
+ * The following transitions can occur while in the Single state:
+ * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, the
+ * state transitions to Dial
+ * \li If a \ref ast_channel_snapshot is received indicating that the channel
+ * has hung up, the state transitions to Finalized
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge
+ *
+ * \par Dial
+ *
+ * This state represents a dial that is occurring within Asterisk. The Party A
+ * can either be the caller for a two party dial, or it can be the dialed party
+ * if the calling party is Asterisk (that is, an Originated channel). In the
+ * first case, the Party B is \em always the dialed channel; in the second case,
+ * the channel is not considered to be a "dialed" channel as it is alone in the
+ * dialed operation.
+ *
+ * While in the Dial state, multiple CDRs can be created for the Party A if a
+ * parallel dial occurs. Each dialed party receives its own CDR with Party A.
+ *
+ * The following transitions can occur while in the Dial state:
+ * \li If a \ref ast_channel_dial_type indicating a Dial End is received where
+ * the \ref dial_status is not ANSWER, the state transitions to Finalized
+ * \li If a \ref ast_channel_snapshot is received indicating that the channel
+ * has hung up, the state transitions to Finalized
+ * \li If a \ref ast_channel_dial_type indicating a Dial End is received where
+ * the \ref dial_status is ANSWER, the state transitions to DialedPending
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge
+ *
+ * \par DialedPending
+ *
+ * Technically, after being dialed, a CDR does not have to transition to the
+ * Bridge state. If the channel being dialed was originated, the channel may
+ * being executing dialplan. Strangely enough, it is also valid to have both
+ * Party A and Party B - after a dial - to not be bridged and instead execute
+ * dialplan. DialedPending handles the state where we figure out if the CDR
+ * showing the dial needs to move to the Bridge state; if the CDR should show
+ * that we started executing dialplan; of if we need a new CDR.
+ *
+ * The following transition can occur while in the DialedPending state:
+ * \li If a \ref ast_channel_snapshot is received that indicates that the
+ * channel has begun executing dialplan, we transition to the Finalized state
+ * if we have a Party B. Otherwise, we transition to the Single state.
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge (through the Dial state)
+ *
+ * \par Bridge
+ *
+ * The Bridge state represents a path of communication between Party A and one
+ * or more other parties. When a CDR enters into the Bridge state, the following
+ * occurs:
+ * \li The CDR attempts to find a Party B. If the CDR has a Party B, it looks
+ * for that channel in the bridge and updates itself accordingly. If the CDR
+ * does not yet have a Party B, it attempts to find a channel that can be its
+ * Party B. If it finds one, it updates itself; otherwise, the CDR is
+ * temporarily finalized.
+ * \li Once the CDR has a Party B or it is determined that it cannot have a
+ * Party B, new CDRs are created for each pairing of channels with the CDR's
+ * Party A.
+ *
+ * As an example, consider the following:
+ * \li A Dials B - both answer
+ * \li B joins a bridge. Since no one is in the bridge and it was a dialed
+ * channel, it cannot have a Party B.
+ * \li A joins the bridge. Since A's Party B is B, A updates itself with B.
+ * \li Now say an Originated channel, C, joins the bridge. The bridge becomes
+ * a multi-party bridge.
+ * \li C attempts to get a Party B. A cannot be C's Party B, as it was created
+ * before it. B is a dialed channel and can thus be C's Party B, so C's CDR
+ * updates its Party B to B.
+ * \li New CDRs are now generated. A gets a new CDR for A -> C. B is dialed, and
+ * hence cannot get any CDR.
+ * \li Now say another Originated channel, D, joins the bridge. Say D has the
+ * \ref party_a flag set on it, such that it is always the preferred Party A.
+ * As such, it takes A as its Party B.
+ * \li New CDRs are generated. D gets new CDRs for D -> B and D -> C.
+ *
+ * The following transitions can occur while in the Bridge state:
+ * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
+ * the state transitions to the Pending state
+ *
+ * \par Pending
+ *
+ * After a channel leaves a bridge, we often don't know what's going to happen
+ * to it. It can enter another bridge; it can be hung up; it can continue on
+ * in the dialplan. It can even enter into limbo! Pending holds the state of the
+ * CDR until we get a subsequent Stasis message telling us what should happen.
+ *
+ * The following transitions can occur while in the Pending state:
+ * \li If a \ref ast_bridge_blob_type message is received, a new CDR is created
+ * and it is transitioned to the Bridge state
+ * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, a
+ * new CDR is created and it is transitioned to the Dial state
+ * \li If a \ref ast_channel_cache_update is received indicating a change in
+ * Context/Extension/Priority, a new CDR is created and transitioned to the
+ * Single state. If the update indicates that the party has been hung up, the
+ * CDR is transitioned to the Finalized state.
+ *
+ * \par Finalized
+ *
+ * Once a CDR enters the finalized state, it is finished. No further updates
+ * can be made to the party information, and the CDR cannot be changed.
+ *
+ * One exception to this occurs during linkedid propagation, in which the CDRs
+ * linkedids are updated based on who the channel is bridged with. In general,
+ * however, a finalized CDR is waiting for dispatch to the CDR backends.
+ */
+
+/*! \brief CDR engine settings */
+enum ast_cdr_settings {
+ CDR_ENABLED = 1 << 0, /*< Enable CDRs */
+ CDR_BATCHMODE = 1 << 1, /*< Whether or not we should dispatch CDRs in batches */
+ CDR_UNANSWERED = 1 << 2, /*< Log unanswered CDRs */
+ CDR_CONGESTION = 1 << 3, /*< Treat congestion as if it were a failed call */
+ CDR_END_BEFORE_H_EXTEN = 1 << 4, /*< End the CDR before the 'h' extension runs */
+ CDR_INITIATED_SECONDS = 1 << 5, /*< Include microseconds into the billing time */
+ CDR_DEBUG = 1 << 6, /*< Enables extra debug statements */
+};
-#include "asterisk/data.h"
+/*! \brief CDR Batch Mode settings */
+enum ast_cdr_batch_mode_settings {
+ BATCH_MODE_SCHEDULER_ONLY = 1 << 0, /*< Don't spawn a thread to handle the batches - do it on the scheduler */
+ BATCH_MODE_SAFE_SHUTDOWN = 1 << 1, /*< During safe shutdown, submit the batched CDRs */
+};
/*!
- * \brief CDR Flags
+ * \brief CDR manipulation options. Certain function calls will manipulate the
+ * state of a CDR object based on these flags.
*/
-enum {
- AST_CDR_FLAG_KEEP_VARS = (1 << 0),
- AST_CDR_FLAG_POSTED = (1 << 1),
- AST_CDR_FLAG_LOCKED = (1 << 2),
- AST_CDR_FLAG_CHILD = (1 << 3),
- AST_CDR_FLAG_POST_DISABLED = (1 << 4),
- AST_CDR_FLAG_BRIDGED = (1 << 5),
- AST_CDR_FLAG_MAIN = (1 << 6),
- AST_CDR_FLAG_ENABLE = (1 << 7),
- AST_CDR_FLAG_ANSLOCKED = (1 << 8),
- AST_CDR_FLAG_DONT_TOUCH = (1 << 9),
- AST_CDR_FLAG_POST_ENABLE = (1 << 10),
- AST_CDR_FLAG_DIALED = (1 << 11),
- AST_CDR_FLAG_ORIGINATED = (1 << 12),
+enum ast_cdr_options {
+ AST_CDR_FLAG_KEEP_VARS = (1 << 0), /*< Copy variables during the operation */
+ AST_CDR_FLAG_DISABLE = (1 << 1), /*< Disable the current CDR */
+ AST_CDR_FLAG_DISABLE_ALL = (3 << 1), /*< Disable the CDR and all future CDRs */
+ AST_CDR_FLAG_PARTY_A = (1 << 3), /*< Set the channel as party A */
+ AST_CDR_FLAG_FINALIZE = (1 << 4), /*< Finalize the current CDRs */
+ AST_CDR_FLAG_SET_ANSWER = (1 << 5), /*< If the channel is answered, set the answer time to now */
+ AST_CDR_FLAG_RESET = (1 << 6), /*< If set, set the start and answer time to now */
};
/*!
* \brief CDR Flags - Disposition
*/
-enum {
+enum ast_cdr_disposition {
AST_CDR_NOANSWER = 0,
AST_CDR_NULL = (1 << 0),
AST_CDR_FAILED = (1 << 1),
@@ -61,21 +244,16 @@ enum {
AST_CDR_CONGESTION = (1 << 4),
};
-/*!
- * \brief CDR AMA Flags
- */
-enum {
- AST_CDR_OMIT = 1,
- AST_CDR_BILLING = 2,
- AST_CDR_DOCUMENTATION = 3,
-};
-
-#define AST_MAX_USER_FIELD 256
-#define AST_MAX_ACCOUNT_CODE 20
-/* Include channel.h after relevant declarations it will need */
-#include "asterisk/channel.h"
-#include "asterisk/utils.h"
+/*! \brief The global options available for CDRs */
+struct ast_cdr_config {
+ struct ast_flags settings; /*< CDR settings */
+ struct batch_settings {
+ unsigned int time; /*< Time between batches */
+ unsigned int size; /*< Size to trigger a batch */
+ struct ast_flags settings; /*< Settings for batches */
+ } batch_settings;
+};
/*!
* \brief Responsible for call detail data
@@ -133,249 +311,186 @@ struct ast_cdr {
struct ast_cdr *next;
};
-int ast_cdr_isset_unanswered(void);
-int ast_cdr_isset_congestion(void);
-void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw);
-int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur);
-int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur);
-void ast_cdr_free_vars(struct ast_cdr *cdr, int recur);
-int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr);
-
-/*!
- * \brief CDR backend callback
- * \warning CDR backends should NOT attempt to access the channel associated
- * with a CDR record. This channel is not guaranteed to exist when the CDR
- * backend is invoked.
- */
-typedef int (*ast_cdrbe)(struct ast_cdr *cdr);
-
-/*! \brief Return TRUE if CDR subsystem is enabled */
-int check_cdr_enabled(void);
-
/*!
- * \brief Allocate a CDR record
- * \retval a malloc'd ast_cdr structure
- * \retval NULL on error (malloc failure)
- */
-struct ast_cdr *ast_cdr_alloc(void);
-
-/*!
- * \brief Duplicate a record and increment the sequence number.
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \see ast_cdr_dup()
- * \see ast_cdr_dup_unique_swap()
- */
-struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr);
-
-/*!
- * \brief Duplicate a record and increment the sequence number of the old
- * record.
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \note This version increments the original CDR's sequence number rather than
- * the duplicate's sequence number. The effect is as if the original CDR's
- * sequence number was swapped with the duplicate's sequence number.
+ * \since 12
+ * \brief Obtain the current CDR configuration
*
- * \see ast_cdr_dup()
- * \see ast_cdr_dup_unique()
- */
-struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr);
-
-/*!
- * \brief Duplicate a record
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \see ast_cdr_dup_unique()
- * \see ast_cdr_dup_unique_swap()
- */
-struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr);
-
-/*!
- * \brief Free a CDR record
- * \param cdr ast_cdr structure to free
- * Returns nothing
- */
-void ast_cdr_free(struct ast_cdr *cdr);
-
-/*!
- * \brief Discard and free a CDR record
- * \param cdr ast_cdr structure to free
- * Returns nothing -- same as free, but no checks or complaints
+ * The configuration is a ref counted object. The caller of this function must
+ * decrement the ref count when finished with the configuration.
+ *
+ * \retval NULL on error
+ * \retval The current CDR configuration
*/
-void ast_cdr_discard(struct ast_cdr *cdr);
+struct ast_cdr_config *ast_cdr_get_config(void);
/*!
- * \brief Initialize based on a channel
- * \param cdr Call Detail Record to use for channel
- * \param chan Channel to bind CDR with
- * Initializes a CDR and associates it with a particular channel
- * \note The channel should be locked before calling.
- * \return 0 by default
+ * \since 12
+ * \brief Set the current CDR configuration
+ *
+ * \param config The new CDR configuration
*/
-int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *chan);
+void ast_cdr_set_config(struct ast_cdr_config *config);
/*!
- * \brief Initialize based on a channel
- * \param cdr Call Detail Record to use for channel
- * \param chan Channel to bind CDR with
- * Initializes a CDR and associates it with a particular channel
- * \note The channel should be locked before calling.
- * \return 0 by default
+ * \since 12
+ * \brief Format a CDR variable from an already posted CDR
+ *
+ * \param cdr The dispatched CDR to process
+ * \param name The name of the variable
+ * \param ret Pointer to the formatted buffer
+ * \param workspace A pointer to the buffer to use to format the variable
+ * \param workspacelen The size of \ref workspace
+ * \param raw If non-zero and a date/time is extraced, provide epoch seconds. Otherwise format as a date/time stamp
*/
-int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *chan);
+void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw);
/*!
- * \brief Register a CDR handling engine
- * \param name name associated with the particular CDR handler
- * \param desc description of the CDR handler
- * \param be function pointer to a CDR handler
- * Used to register a Call Detail Record handler.
- * \retval 0 on success.
- * \retval -1 on error
+ * \since 12
+ * \brief Retrieve a CDR variable from a channel's current CDR
+ *
+ * \param channel_name The name of the party A channel that the CDR is associated with
+ * \param name The name of the variable to retrieve
+ * \param value Buffer to hold the value
+ * \param length The size of the buffer
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
*/
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
+int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length);
/*!
- * \brief Unregister a CDR handling engine
- * \param name name of CDR handler to unregister
- * Unregisters a CDR by it's name
+ * \since 12
+ * \brief Set a variable on a CDR
+ *
+ * \param channel_name The channel to set the variable on
+ * \param name The name of the variable to set
+ * \param value The value of the variable to set
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
*/
-void ast_cdr_unregister(const char *name);
+int ast_cdr_setvar(const char *channel_name, const char *name, const char *value);
/*!
- * \brief Start a call
- * \param cdr the cdr you wish to associate with the call
- * Starts all CDR stuff necessary for monitoring a call
- * Returns nothing
- */
-void ast_cdr_start(struct ast_cdr *cdr);
-
-/*! \brief Answer a call
- * \param cdr the cdr you wish to associate with the call
- * Starts all CDR stuff necessary for doing CDR when answering a call
- * \note NULL argument is just fine.
+ * \since 12
+ * \brief Fork a CDR
+ *
+ * \param channel_name The name of the channel whose CDR should be forked
+ * \param options Options to control how the fork occurs.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
*/
-void ast_cdr_answer(struct ast_cdr *cdr);
+int ast_cdr_fork(const char *channel_name, struct ast_flags *options);
/*!
- * \brief A call wasn't answered
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "NO ANSWER"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
+ * \since 12
+ * \brief Set a property on a CDR for a channel
+ *
+ * This function sets specific administrative properties on a CDR for a channel.
+ * This includes properties like preventing a CDR from being dispatched, to
+ * setting the channel as the preferred Party A in future CDRs. See
+ * \ref enum ast_cdr_options for more information.
+ *
+ * \param channel_name The CDR's channel
+ * \param option Option to apply to the CDR
+ *
+ * \retval 0 on success
+ * \retval 1 on error
*/
-extern void ast_cdr_noanswer(struct ast_cdr *cdr);
+int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option);
/*!
- * \brief A call was set to congestion
- * \param cdr the cdr you wish to associate with the call
- * Markst he channel disposition as "CONGESTION"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application
+ * \since 12
+ * \brief Clear a property on a CDR for a channel
+ *
+ * Clears a flag previously set by \ref ast_cdr_set_property
+ *
+ * \param channel_name The CDR's channel
+ * \param option Option to clear from the CDR
+ *
+ * \retval 0 on success
+ * \retval 1 on error
*/
-extern void ast_cdr_congestion(struct ast_cdr *cdr);
+int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option);
/*!
- * \brief Busy a call
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "BUSY"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
- * Returns nothing
+ * \brief Reset the detail record
+ * \param channel_name The channel that the CDR is associated with
+ * \param options Options that control what the reset operation does.
+ *
+ * Valid options are:
+ * \ref AST_CDR_FLAG_KEEP_VARS - keep the variables during the reset
+ * \ref AST_CDR_FLAG_DISABLE_ALL - when used with \ref ast_cdr_reset, re-enables
+ * the CDR
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
*/
-void ast_cdr_busy(struct ast_cdr *cdr);
+int ast_cdr_reset(const char *channel_name, struct ast_flags *options);
/*!
- * \brief Fail a call
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "FAILED"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
- * Returns nothing
+ * \brief Serializes all the data and variables for a current CDR record
+ * \param channel_name The channel to get the CDR for
+ * \param buf A buffer to use for formatting the data
+ * \param delim A delimeter to use to separate variable keys/values
+ * \param sep A separator to use between nestings
+ * \retval the total number of serialized variables
*/
-void ast_cdr_failed(struct ast_cdr *cdr);
+int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep);
/*!
- * \brief Save the result of the call based on the AST_CAUSE_*
- * \param cdr the cdr you wish to associate with the call
- * \param cause the AST_CAUSE_*
- * Returns nothing
+ * \brief CDR backend callback
+ * \warning CDR backends should NOT attempt to access the channel associated
+ * with a CDR record. This channel is not guaranteed to exist when the CDR
+ * backend is invoked.
*/
-int ast_cdr_disposition(struct ast_cdr *cdr, int cause);
+typedef int (*ast_cdrbe)(struct ast_cdr *cdr);
-/*!
- * \brief End a call
- * \param cdr the cdr you have associated the call with
- * Registers the end of call time in the cdr structure.
- * Returns nothing
- */
-void ast_cdr_end(struct ast_cdr *cdr);
+/*! \brief Return TRUE if CDR subsystem is enabled */
+int ast_cdr_is_enabled(void);
/*!
- * \brief Detaches the detail record for posting (and freeing) either now or at a
- * later time in bulk with other records during batch mode operation.
- * \param cdr Which CDR to detach from the channel thread
- * Prevents the channel thread from blocking on the CDR handling
- * Returns nothing
+ * \brief Allocate a CDR record
+ * \retval a malloc'd ast_cdr structure
+ * \retval NULL on error (malloc failure)
*/
-void ast_cdr_detach(struct ast_cdr *cdr);
+struct ast_cdr *ast_cdr_alloc(void);
-/*!
- * \brief Spawns (possibly) a new thread to submit a batch of CDRs to the backend engines
- * \param shutdown Whether or not we are shutting down
- * Blocks the asterisk shutdown procedures until the CDR data is submitted.
- * Returns nothing
- */
-void ast_cdr_submit_batch(int shutdown);
/*!
- * \brief Set the destination channel, if there was one
- * \param cdr Which cdr it's applied to
- * \param chan Channel to which dest will be
- * Sets the destination channel the CDR is applied to
- * Returns nothing
+ * \brief Duplicate a public CDR
+ * \param cdr the record to duplicate
+ *
+ * \retval a malloc'd ast_cdr structure,
+ * \retval NULL on error (malloc failure)
*/
-void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chan);
+struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr);
/*!
- * \brief Set the last executed application
- * \param cdr which cdr to act upon
- * \param app the name of the app you wish to change it to
- * \param data the data you want in the data field of app you set it to
- * Changes the value of the last executed app
+ * \brief Free a CDR record
+ * \param cdr ast_cdr structure to free
* Returns nothing
*/
-void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data);
-
-/*!
- * \brief Set the answer time for a call
- * \param cdr the cdr you wish to associate with the call
- * \param t the answer time
- * Starts all CDR stuff necessary for doing CDR when answering a call
- * NULL argument is just fine.
- */
-void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t);
+void ast_cdr_free(struct ast_cdr *cdr);
/*!
- * \brief Set the disposition for a call
- * \param cdr the cdr you wish to associate with the call
- * \param disposition the new disposition
- * Set the disposition on a call.
- * NULL argument is just fine.
+ * \brief Register a CDR handling engine
+ * \param name name associated with the particular CDR handler
+ * \param desc description of the CDR handler
+ * \param be function pointer to a CDR handler
+ * Used to register a Call Detail Record handler.
+ * \retval 0 on success.
+ * \retval -1 on error
*/
-void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition);
+int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
/*!
- * \brief Convert a string to a detail record AMA flag
- * \param flag string form of flag
- * Converts the string form of the flag to the binary form.
- * \return the binary form of the flag
+ * \brief Unregister a CDR handling engine
+ * \param name name of CDR handler to unregister
+ * Unregisters a CDR by it's name
*/
-int ast_cdr_amaflags2int(const char *flag);
+void ast_cdr_unregister(const char *name);
/*!
* \brief Disposition to a string
@@ -383,81 +498,15 @@ int ast_cdr_amaflags2int(const char *flag);
* Converts the binary form of a disposition to string form.
* \return a pointer to the string form
*/
-char *ast_cdr_disp2str(int disposition);
-
-/*!
- * \brief Reset the detail record, optionally posting it first
- * \param cdr which cdr to act upon
- * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
- * |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
- */
-void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *flags);
-
-/*! Reset the detail record times, flags */
-/*!
- * \param cdr which cdr to act upon
- * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
- * |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
- */
-void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *flags);
-
-/*! Flags to a string */
-/*!
- * \param flags binary flag
- * Converts binary flags to string flags
- * Returns string with flag name
- */
-char *ast_cdr_flags2str(int flags);
-
-/*!
- * \brief Move the non-null data from the "from" cdr to the "to" cdr
- * \param to the cdr to get the goodies
- * \param from the cdr to give the goodies
- */
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from);
-
-/*!
- * \brief Set account code, will generate AMI event
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setaccount(struct ast_channel *chan, const char *account);
-
-/*!
- * \brief Set the peer account
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account);
-
-/*!
- * \brief Set AMA flags for channel
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setamaflags(struct ast_channel *chan, const char *amaflags);
+const char *ast_cdr_disp2str(int disposition);
/*!
* \brief Set CDR user field for channel (stored in CDR)
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield);
-/*!
- * \brief Append to CDR user field for channel (stored in CDR)
- * \note The channel should be locked before calling.
- */
-int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield);
-
-
-/*!
- * \brief Update CDR on a channel
- * \note The channel should be locked before calling.
+ *
+ * \param channel_name The name of the channel that owns the CDR
+ * \param userfield The user field to set
*/
-int ast_cdr_update(struct ast_channel *chan);
-
-
-extern int ast_default_amaflags;
-
-extern char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
-
-struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr);
+void ast_cdr_setuserfield(const char *channel_name, const char *userfield);
/*! \brief Reload the configuration file cdr.conf and start/stop CDR scheduling thread */
int ast_cdr_engine_reload(void);
@@ -468,14 +517,4 @@ int ast_cdr_engine_init(void);
/*! Submit any remaining CDRs and prepare for shutdown */
void ast_cdr_engine_term(void);
-/*!
- * \brief
- * \param[in] tree Where to insert the cdr.
- * \param[in] cdr The cdr structure to insert in 'tree'.
- * \param[in] recur Go throw all the cdr levels.
- * \retval <0 on error.
- * \retval 0 on success.
- */
-int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur);
-
#endif /* _ASTERISK_CDR_H */
diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h
index 034a96ab9..914037d4c 100644
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -36,20 +36,6 @@ extern "C" {
#include "asterisk/event.h"
/*!
- * \brief AMA Flags
- *
- * \note This must much up with the AST_CDR_* defines for AMA flags.
- */
-enum ast_cel_ama_flag {
- AST_CEL_AMA_FLAG_NONE,
- AST_CEL_AMA_FLAG_OMIT,
- AST_CEL_AMA_FLAG_BILLING,
- AST_CEL_AMA_FLAG_DOCUMENTATION,
- /*! \brief Must be final entry */
- AST_CEL_AMA_FLAG_TOTAL,
-};
-
-/*!
* \brief CEL event types
*/
enum ast_cel_event_type {
@@ -162,17 +148,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type);
*/
enum ast_cel_event_type ast_cel_str_to_event_type(const char *name);
-/*!
- * \brief Convert AMA flag to printable string
- *
- * \param[in] flag the flag to convert to a string
- *
- * \since 1.8
- *
- * \return the string representation of the flag
- */
-const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag);
-
/*!
* \brief Check and potentially retire a Linked ID
*
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 42d50f21b..1b36c14ae 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -131,11 +131,13 @@ References:
extern "C" {
#endif
-#define AST_MAX_EXTENSION 80 /*!< Max length of an extension */
-#define AST_MAX_CONTEXT 80 /*!< Max length of a context */
-#define AST_CHANNEL_NAME 80 /*!< Max length of an ast_channel name */
-#define MAX_LANGUAGE 40 /*!< Max length of the language setting */
-#define MAX_MUSICCLASS 80 /*!< Max length of the music class setting */
+#define AST_MAX_EXTENSION 80 /*!< Max length of an extension */
+#define AST_MAX_CONTEXT 80 /*!< Max length of a context */
+#define AST_MAX_ACCOUNT_CODE 20 /*!< Max length of an account code */
+#define AST_CHANNEL_NAME 80 /*!< Max length of an ast_channel name */
+#define MAX_LANGUAGE 40 /*!< Max length of the language setting */
+#define MAX_MUSICCLASS 80 /*!< Max length of the music class setting */
+#define AST_MAX_USER_FIELD 256 /*!< Max length of the channel user field */
#include "asterisk/frame.h"
#include "asterisk/chanvars.h"
@@ -915,6 +917,10 @@ enum {
* to continue.
*/
AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT = (1 << 22),
+ /*!
+ * This flag indicates that the channel was originated.
+ */
+ AST_FLAG_ORIGINATED = (1 << 23),
};
/*! \brief ast_bridge_config flags */
@@ -1028,6 +1034,16 @@ enum channelreloadreason {
};
/*!
+ * \brief Channel AMA Flags
+ */
+enum ama_flags {
+ AST_AMA_NONE = 0,
+ AST_AMA_OMIT,
+ AST_AMA_BILLING,
+ AST_AMA_DOCUMENTATION,
+};
+
+/*!
* \note None of the datastore API calls lock the ast_channel they are using.
* So, the channel should be locked before calling the functions that
* take a channel argument.
@@ -1100,7 +1116,7 @@ struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 14)))
__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, enum ama_flags amaflag,
const char *file, int line, const char *function,
const char *name_fmt, ...);
@@ -1591,7 +1607,6 @@ int ast_answer(struct ast_channel *chan);
* \brief Answer a channel
*
* \param chan channel to answer
- * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered'
*
* This function answers a channel and handles all necessary call
* setup functions.
@@ -1607,14 +1622,13 @@ int ast_answer(struct ast_channel *chan);
* \retval 0 on success
* \retval non-zero on failure
*/
-int ast_raw_answer(struct ast_channel *chan, int cdr_answer);
+int ast_raw_answer(struct ast_channel *chan);
/*!
* \brief Answer a channel, with a selectable delay before returning
*
* \param chan channel to answer
* \param delay maximum amount of time to wait for incoming media
- * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered'
*
* This function answers a channel and handles all necessary call
* setup functions.
@@ -1630,7 +1644,7 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer);
* \retval 0 on success
* \retval non-zero on failure
*/
-int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer);
+int __ast_answer(struct ast_channel *chan, unsigned int delay);
/*!
* \brief Execute a Gosub call on the channel before a call is placed.
@@ -2197,6 +2211,28 @@ int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen,
void ast_deactivate_generator(struct ast_channel *chan);
/*!
+ * \since 12
+ * \brief Obtain how long the channel since the channel was created
+ *
+ * \param chan The channel object
+ *
+ * \retval 0 if the time value cannot be computed (or you called this really fast)
+ * \retval The number of seconds the channel has been up
+ */
+int ast_channel_get_duration(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Obtain how long it has been since the channel was answered
+ *
+ * \param chan The channel object
+ *
+ * \retval 0 if the channel isn't answered (or you called this really fast)
+ * \retval The number of seconds the channel has been up
+ */
+int ast_channel_get_up_time(struct ast_channel *chan);
+
+/*!
* \brief Set caller ID number, name and ANI and generate AMI event.
*
* \note Use ast_channel_set_caller() and ast_channel_set_caller_event() instead.
@@ -2728,12 +2764,6 @@ struct ast_channel *ast_channel_get_by_exten(const char *exten, const char *cont
/*! @} End channel search functions. */
/*!
- \brief propagate the linked id between chan and peer
- */
-void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer);
-
-
-/*!
* \brief Initialize the given name structure.
* \since 1.8
*
@@ -3806,6 +3836,26 @@ void ast_channel_unlink(struct ast_channel *chan);
*/
void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast_control_pvt_cause_code *cause_code, int datalen);
+/*!
+ * \since 12
+ * \brief Convert a string to a detail record AMA flag
+ *
+ * \param flag string form of flag
+ *
+ * \retval the enum (integer) form of the flag
+ */
+enum ama_flags ast_channel_string2amaflag(const char *flag);
+
+/*!
+ * \since 12
+ * \brief Convert the enum representation of an AMA flag to a string representation
+ *
+ * \param flags integer flag
+ *
+ * \retval A string representation of the flag
+ */
+const char *ast_channel_amaflags2string(enum ama_flags flags);
+
/* ACCESSOR FUNTIONS */
/*! \brief Set the channel name */
void ast_channel_name_set(struct ast_channel *chan, const char *name);
@@ -3863,8 +3913,8 @@ char ast_channel_sending_dtmf_digit(const struct ast_channel *chan);
void ast_channel_sending_dtmf_digit_set(struct ast_channel *chan, char value);
struct timeval ast_channel_sending_dtmf_tv(const struct ast_channel *chan);
void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval value);
-int ast_channel_amaflags(const struct ast_channel *chan);
-void ast_channel_amaflags_set(struct ast_channel *chan, int value);
+enum ama_flags ast_channel_amaflags(const struct ast_channel *chan);
+void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value);
int ast_channel_epfd(const struct ast_channel *chan);
void ast_channel_epfd_set(struct ast_channel *chan, int value);
int ast_channel_fdno(const struct ast_channel *chan);
@@ -3988,6 +4038,8 @@ void ast_channel_whentohangup_set(struct ast_channel *chan, struct timeval *valu
void ast_channel_varshead_set(struct ast_channel *chan, struct varshead *value);
struct timeval ast_channel_creationtime(struct ast_channel *chan);
void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *value);
+struct timeval ast_channel_answertime(struct ast_channel *chan);
+void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value);
/* List getters */
struct ast_hangup_handler_list *ast_channel_hangup_handlers(struct ast_channel *chan);
@@ -4278,4 +4330,27 @@ int ast_channel_move(struct ast_channel *dest, struct ast_channel *source);
*/
int ast_channel_forward_endpoint(struct ast_channel *chan, struct ast_endpoint *endpoint);
+/*!
+ * \brief Return the oldest linkedid between two channels.
+ *
+ * A channel linkedid is derived from the channel 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.
+ *
+ * \param a The linkedid value of the first channel to compare
+ * \param b The linkedid value of the second channel to compare
+ *
+ * \retval NULL on failure
+ * \retval The oldest linkedid value
+ * \since 12.0.0
+*/
+const char *ast_channel_oldest_linkedid(const char *a, const char *b);
+
+
#endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h
index 38776c1e1..a54a1b848 100644
--- a/include/asterisk/channel_internal.h
+++ b/include/asterisk/channel_internal.h
@@ -18,8 +18,8 @@
* \brief Internal channel functions for channel.c to use
*/
-#define ast_channel_internal_alloc(destructor) __ast_channel_internal_alloc(destructor, __FILE__, __LINE__, __PRETTY_FUNCTION__)
-struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function);
+#define ast_channel_internal_alloc(destructor, linkedid) __ast_channel_internal_alloc(destructor, linkedid, __FILE__, __LINE__, __PRETTY_FUNCTION__)
+struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function);
void ast_channel_internal_finalize(struct ast_channel *chan);
int ast_channel_internal_is_finalized(struct ast_channel *chan);
void ast_channel_internal_cleanup(struct ast_channel *chan);
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 339c77274..ca075ae69 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -38,36 +38,38 @@
*/
struct ast_channel_snapshot {
AST_DECLARE_STRING_FIELDS(
- AST_STRING_FIELD(name); /*!< ASCII unique channel name */
- AST_STRING_FIELD(accountcode); /*!< Account code for billing */
- AST_STRING_FIELD(peeraccount); /*!< Peer account code for billing */
- AST_STRING_FIELD(userfield); /*!< Userfield for CEL billing */
- AST_STRING_FIELD(uniqueid); /*!< Unique Channel Identifier */
- AST_STRING_FIELD(linkedid); /*!< Linked Channel Identifier -- gets propagated by linkage */
- AST_STRING_FIELD(parkinglot); /*!< Default parking lot, if empty, default parking lot */
- AST_STRING_FIELD(hangupsource); /*!< Who is responsible for hanging up this channel */
- AST_STRING_FIELD(appl); /*!< Current application */
- AST_STRING_FIELD(data); /*!< Data passed to current application */
- AST_STRING_FIELD(context); /*!< Dialplan: Current extension context */
- AST_STRING_FIELD(exten); /*!< Dialplan: Current extension number */
- AST_STRING_FIELD(caller_name); /*!< Caller ID Name */
- AST_STRING_FIELD(caller_number); /*!< Caller ID Number */
- AST_STRING_FIELD(caller_ani); /*!< Caller ID ANI Number */
- AST_STRING_FIELD(caller_rdnis); /*!< Caller ID RDNIS Number */
- AST_STRING_FIELD(caller_dnid); /*!< Caller ID DNID Number */
- AST_STRING_FIELD(connected_name); /*!< Connected Line Name */
- AST_STRING_FIELD(connected_number); /*!< Connected Line Number */
- AST_STRING_FIELD(language); /*!< The default spoken language for the channel */
+ AST_STRING_FIELD(name); /*!< ASCII unique channel name */
+ AST_STRING_FIELD(accountcode); /*!< Account code for billing */
+ AST_STRING_FIELD(peeraccount); /*!< Peer account code for billing */
+ AST_STRING_FIELD(userfield); /*!< Userfield for CEL billing */
+ AST_STRING_FIELD(uniqueid); /*!< Unique Channel Identifier */
+ AST_STRING_FIELD(linkedid); /*!< Linked Channel Identifier -- gets propagated by linkage */
+ AST_STRING_FIELD(parkinglot); /*!< Default parking lot, if empty, default parking lot */
+ AST_STRING_FIELD(hangupsource); /*!< Who is responsible for hanging up this channel */
+ AST_STRING_FIELD(appl); /*!< Current application */
+ AST_STRING_FIELD(data); /*!< Data passed to current application */
+ AST_STRING_FIELD(context); /*!< Dialplan: Current extension context */
+ AST_STRING_FIELD(exten); /*!< Dialplan: Current extension number */
+ AST_STRING_FIELD(caller_name); /*!< Caller ID Name */
+ AST_STRING_FIELD(caller_number); /*!< Caller ID Number */
+ AST_STRING_FIELD(caller_dnid); /*!< Dialed ID Number */
+ AST_STRING_FIELD(caller_ani); /*< Caller ID ANI Number */
+ AST_STRING_FIELD(caller_rdnis); /*!< Caller ID RDNIS Number */
+ AST_STRING_FIELD(caller_subaddr); /*!< Caller subaddress */
+ AST_STRING_FIELD(dialed_subaddr); /*!< Dialed subaddress */
+ AST_STRING_FIELD(connected_name); /*!< Connected Line Name */
+ AST_STRING_FIELD(connected_number); /*!< Connected Line Number */
+ AST_STRING_FIELD(language); /*!< The default spoken language for the channel */
);
- struct timeval creationtime; /*!< The time of channel creation */
- enum ast_channel_state state; /*!< State of line */
- int priority; /*!< Dialplan: Current extension priority */
- int amaflags; /*!< AMA flags for billing */
- int hangupcause; /*!< Why is the channel hanged up. See causes.h */
- int caller_pres; /*!< Caller ID presentation. */
- struct ast_flags flags; /*!< channel flags of AST_FLAG_ type */
- struct varshead *manager_vars; /*!< Variables to be appended to manager events */
+ struct timeval creationtime; /*!< The time of channel creation */
+ enum ast_channel_state state; /*!< State of line */
+ int priority; /*!< Dialplan: Current extension priority */
+ int amaflags; /*!< AMA flags for billing */
+ int hangupcause; /*!< Why is the channel hanged up. See causes.h */
+ int caller_pres; /*!< Caller ID presentation. */
+ struct ast_flags flags; /*!< channel flags of AST_FLAG_ type */
+ struct varshead *manager_vars; /*!< Variables to be appended to manager events */
};
/*!
@@ -300,7 +302,7 @@ void ast_channel_publish_snapshot(struct ast_channel *chan);
* \since 12
* \brief Publish a \ref ast_channel_varset for a channel.
*
- * \param chan Channel to pulish the event for, or \c NULL for 'none'.
+ * \param chan Channel to publish the event for, or \c NULL for 'none'.
* \param variable Name of the variable being set
* \param value Value.
*/
diff --git a/include/asterisk/stasis_internal.h b/include/asterisk/stasis_internal.h
new file mode 100644
index 000000000..67ab88ff0
--- /dev/null
+++ b/include/asterisk/stasis_internal.h
@@ -0,0 +1,69 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef STASIS_INTERNAL_H_
+#define STASIS_INTERNAL_H_
+
+/*! \file
+ *
+ * \brief Internal Stasis APIs.
+ *
+ * This header file is used to define functions that are shared between files that make
+ * up \ref stasis. Functions declared here should not be used by any module outside of
+ * Stasis.
+ *
+ * If you find yourself needing to call one of these functions directly, something has
+ * probably gone horribly wrong.
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ */
+
+struct stasis_topic;
+struct stasis_subscription;
+struct stasis_message;
+
+/*!
+ * \brief Create a subscription.
+ *
+ * In addition to being AO2 managed memory (requiring an ao2_cleanup() to free
+ * up this reference), the subscription must be explicitly unsubscribed from its
+ * topic using stasis_unsubscribe().
+ *
+ * The invocations of the callback are serialized, but may not always occur on
+ * the same thread. The invocation order of different subscriptions is
+ * unspecified.
+ *
+ * Note: modules outside of Stasis should use \ref stasis_subscribe.
+ *
+ * \param topic Topic to subscribe to.
+ * \param callback Callback function for subscription messages.
+ * \param data Data to be passed to the callback, in addition to the message.
+ * \param needs_mailbox Determines whether or not the subscription requires a mailbox.
+ * Subscriptions with mailboxes will be delivered on a thread in the Stasis threadpool;
+ * subscriptions without mailboxes will be delivered on the publisher thread.
+ * \return New \ref stasis_subscription object.
+ * \return \c NULL on error.
+ * \since 12
+ */
+struct stasis_subscription *internal_stasis_subscribe(
+ struct stasis_topic *topic,
+ void (*stasis_subscription_cb)(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message),
+ void *data,
+ int needs_mailbox);
+
+#endif /* STASIS_INTERNAL_H_ */
diff --git a/include/asterisk/test.h b/include/asterisk/test.h
index d890b7a3e..d29969f8b 100644
--- a/include/asterisk/test.h
+++ b/include/asterisk/test.h
@@ -238,7 +238,9 @@ struct ast_test_info {
/*!
* \brief Generic test callback function
*
- * \param error buffer string for failure results
+ * \param info The test info object
+ * \param cmd What to perform in the test
+ * \param test The actual test object being manipulated
*
* \retval AST_TEST_PASS for pass
* \retval AST_TEST_FAIL for failure
@@ -247,6 +249,30 @@ typedef enum ast_test_result_state (ast_test_cb_t)(struct ast_test_info *info,
enum ast_test_command cmd, struct ast_test *test);
/*!
+ * \since 12
+ * \brief A test initialization callback function
+ *
+ * \param info The test info object
+ * \param test The actual test object that will be manipulated
+ *
+ * \retval 0 success
+ * \retval other failure. This will fail the test.
+ */
+typedef int (ast_test_init_cb_t)(struct ast_test_info *info, struct ast_test *test);
+
+/*!
+ * \since 12
+ * \brief A test cleanup callback function
+ *
+ * \param info The test info object
+ * \param test The actual test object that was executed
+ *
+ * \retval 0 success
+ * \retval other failure. This will fail the test.
+ */
+typedef int (ast_test_cleanup_cb_t)(struct ast_test_info *info, struct ast_test *test);
+
+/*!
* \brief unregisters a test with the test framework
*
* \param test callback function (required)
@@ -267,6 +293,40 @@ int ast_test_unregister(ast_test_cb_t *cb);
int ast_test_register(ast_test_cb_t *cb);
/*!
+ * \since 12
+ * \brief Register an initialization function to be run before each test
+ * executes
+ *
+ * This function lets a registered test have an initialization function that
+ * will be run prior to test execution. Each category may have a single init
+ * function.
+ *
+ * If the initialization function returns a non-zero value, the test will not
+ * be executed and the result will be set to \ref AST_TEST_FAIL.
+ *
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_test_register_init(const char *category, ast_test_init_cb_t *cb);
+
+/*!
+ * \since 12
+ * \brief Register a cleanup function to be run after each test executes
+ *
+ * This function lets a registered test have a cleanup function that will be
+ * run immediately after test execution. Each category may have a single
+ * cleanup function.
+ *
+ * If the cleanup function returns a non-zero value, the test result will be
+ * set to \ref AST_TEST_FAIL.
+ *
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb);
+
+
+/*!
* \brief Unit test debug output.
* \since 12.0.0
*
@@ -278,6 +338,17 @@ int ast_test_register(ast_test_cb_t *cb);
void ast_test_debug(struct ast_test *test, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
/*!
+ * \brief Set the result of a test.
+ *
+ * If the caller of this function sets the result to AST_TEST_FAIL, returning
+ * AST_TEST_PASS from the test will not pass the test. This lets a test writer
+ * end and fail a test and continue on with logic, catching multiple failure
+ * conditions within a single test.
+ */
+void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state);
+
+
+/*!
* \brief update test's status during testing.
*
* \param test currently executing test
diff --git a/include/asterisk/time.h b/include/asterisk/time.h
index dd68db704..f2382df33 100644
--- a/include/asterisk/time.h
+++ b/include/asterisk/time.h
@@ -152,6 +152,17 @@ struct timeval ast_tvadd(struct timeval a, struct timeval b);
struct timeval ast_tvsub(struct timeval a, struct timeval b);
/*!
+ * \since 12
+ * \brief Formats a duration into HH:MM:SS
+ *
+ * \param duration The time (in seconds) to format
+ * \param buf A buffer to hold the formatted string'
+ * \param length The size of the buffer
+ */
+void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length);
+
+
+/*!
* \brief Calculate remaining milliseconds given a starting timestamp
* and upper bound
*
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
diff --git a/res/res_agi.c b/res/res_agi.c
index 486310dd6..a841f3623 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -3625,11 +3625,6 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch
the module we are using */
if (c->mod != ast_module_info->self)
ast_module_ref(c->mod);
- /* If the AGI command being executed is an actual application (using agi exec)
- the app field will be updated in pbx_exec via handle_exec */
- if (ast_channel_cdr(chan) && !ast_check_hangup(chan) && strcasecmp(argv[0], "EXEC"))
- ast_cdr_setapp(ast_channel_cdr(chan), "AGI", buf);
-
res = c->handler(chan, agi, argc, argv);
if (c->mod != ast_module_info->self)
ast_module_unref(c->mod);
diff --git a/res/res_config_sqlite.c b/res/res_config_sqlite.c
index e648f941b..7d5fd83e6 100644
--- a/res/res_config_sqlite.c
+++ b/res/res_config_sqlite.c
@@ -791,7 +791,7 @@ static int cdr_handler(struct ast_cdr *cdr)
AST_RWLIST_TRAVERSE(&(tbl->columns), col, list) {
if (col->isint) {
- ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 1);
+ ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 1);
if (!tmp) {
continue;
}
@@ -800,7 +800,7 @@ static int cdr_handler(struct ast_cdr *cdr)
ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", scannum);
}
} else {
- ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 0);
+ ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 0);
if (!tmp) {
continue;
}
diff --git a/res/res_monitor.c b/res/res_monitor.c
index f1da4ec83..b5225010e 100644
--- a/res/res_monitor.c
+++ b/res/res_monitor.c
@@ -692,18 +692,10 @@ static int start_monitor_exec(struct ast_channel *chan, const char *data)
}
if (!ast_strlen_zero(urlprefix) && !ast_strlen_zero(args.fname_base)) {
- struct ast_cdr *chan_cdr;
snprintf(tmp, sizeof(tmp), "%s/%s.%s", urlprefix, args.fname_base,
((strcmp(args.format, "gsm")) ? "wav" : "gsm"));
ast_channel_lock(chan);
- if (!ast_channel_cdr(chan)) {
- if (!(chan_cdr = ast_cdr_alloc())) {
- ast_channel_unlock(chan);
- return -1;
- }
- ast_channel_cdr_set(chan, chan_cdr);
- }
- ast_cdr_setuserfield(chan, tmp);
+ ast_cdr_setuserfield(ast_channel_name(chan), tmp);
ast_channel_unlock(chan);
}
if (waitforbridge) {
diff --git a/res/res_stasis_answer.c b/res/res_stasis_answer.c
index b7534b93d..53d4b06e2 100644
--- a/res/res_stasis_answer.c
+++ b/res/res_stasis_answer.c
@@ -42,10 +42,9 @@ static void *app_control_answer(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
const int delay = 0;
- const int cdr_answer = 1;
ast_debug(3, "%s: Answering",
stasis_app_control_get_channel_id(control));
- return __ast_answer(chan, delay, cdr_answer) == 0 ? &OK : &FAIL;
+ return __ast_answer(chan, delay) == 0 ? &OK : &FAIL;
}
int stasis_app_control_answer(struct stasis_app_control *control)
diff --git a/tests/test_cdr.c b/tests/test_cdr.c
new file mode 100644
index 000000000..c9621a450
--- /dev/null
+++ b/tests/test_cdr.c
@@ -0,0 +1,2413 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief CDR unit tests
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <math.h>
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/cdr.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/time.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+
+#define EPSILON 0.001
+
+#define TEST_CATEGORY "/main/cdr/"
+
+#define MOCK_CDR_BACKEND "mock_cdr_backend"
+
+#define CHANNEL_TECH_NAME "CDRTestChannel"
+
+/*! \brief A placeholder for Asterisk's 'real' CDR configuration */
+static struct ast_cdr_config *saved_config;
+
+/*! \brief A configuration suitable for 'normal' CDRs */
+static struct ast_cdr_config debug_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with unanswered records */
+static struct ast_cdr_config unanswered_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with congestion enabled */
+static struct ast_cdr_config congestion_cdr_config = {
+ .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION,
+};
+
+/*! \brief Macro to swap a configuration out from the CDR engine. This should be
+ * used at the beginning of each test to set the needed configuration for that
+ * test.
+ */
+#define SWAP_CONFIG(ao2_config, template) do { \
+ *(ao2_config) = (template); \
+ ast_cdr_set_config((ao2_config)); \
+ } while (0)
+
+/*! \brief A linked list of received CDR entries from the engine */
+static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE;
+
+/*! \brief The Mock CDR backend condition wait */
+static ast_cond_t mock_cdr_cond;
+
+/*! \brief A channel technology used for the unit tests */
+static struct ast_channel_tech test_cdr_chan_tech = {
+ .type = CHANNEL_TECH_NAME,
+ .description = "Mock channel technology for CDR tests",
+};
+
+struct test_cdr_entry {
+ struct ast_cdr *cdr;
+ AST_LIST_ENTRY(test_cdr_entry) list;
+};
+
+/*! \brief The number of CDRs the mock backend has received */
+static int global_mock_cdr_count;
+
+/*! \internal
+ * \brief Callback function for the mock CDR backend
+ *
+ * This function 'processes' a dispatched CDR record by adding it to the
+ * \ref actual_cdr_entries list. When a test completes, it can verify the
+ * expected records against this list of actual CDRs created by the engine.
+ *
+ * \param cdr The public CDR object created by the engine
+ *
+ * \retval -1 on error
+ * \retval 0 on success
+ */
+static int mock_cdr_backend_cb(struct ast_cdr *cdr)
+{
+ struct ast_cdr *cdr_copy, *cdr_prev = NULL;
+ struct ast_cdr *mock_cdr = NULL;
+ struct test_cdr_entry *cdr_wrapper;
+
+ cdr_wrapper = ast_calloc(1, sizeof(*cdr_wrapper));
+ if (!cdr_wrapper) {
+ return -1;
+ }
+
+ for (; cdr; cdr = cdr->next) {
+ struct ast_var_t *var_entry, *var_copy;
+
+ cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+ if (!cdr_copy) {
+ return -1;
+ }
+ *cdr_copy = *cdr;
+ cdr_copy->varshead.first = NULL;
+ cdr_copy->varshead.last = NULL;
+ cdr_copy->next = NULL;
+
+ AST_LIST_TRAVERSE(&cdr->varshead, var_entry, entries) {
+ var_copy = ast_var_assign(var_entry->name, var_entry->value);
+ if (!var_copy) {
+ return -1;
+ }
+ AST_LIST_INSERT_TAIL(&cdr_copy->varshead, var_copy, entries);
+ }
+
+ if (!mock_cdr) {
+ mock_cdr = cdr_copy;
+ }
+ if (cdr_prev) {
+ cdr_prev->next = cdr_copy;
+ }
+ cdr_prev = cdr_copy;
+ }
+ cdr_wrapper->cdr = mock_cdr;
+
+ AST_LIST_LOCK(&actual_cdr_entries);
+ AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list);
+ global_mock_cdr_count++;
+ ast_cond_signal(&mock_cdr_cond);
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+
+ return 0;
+}
+
+/*! \internal
+ * \brief Remove all entries from \ref actual_cdr_entries
+ */
+static void clear_mock_cdr_backend(void)
+{
+ struct test_cdr_entry *cdr_wrapper;
+
+ AST_LIST_LOCK(&actual_cdr_entries);
+ while ((cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list))) {
+ ast_cdr_free(cdr_wrapper->cdr);
+ ast_free(cdr_wrapper);
+ }
+ global_mock_cdr_count = 0;
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+}
+
+/*! \brief Verify a string field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_STRING_FIELD(field, actual, expected) do { \
+ if (strcmp((actual)->field, (expected)->field)) { \
+ ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Verify a numeric field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \
+ if ((actual)->field != (expected)->field) { \
+ ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Verify a time field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_TIME_VALUE(field, actual) do { \
+ if (ast_tvzero((actual)->field)) { \
+ ast_test_status_update(test, "Field %s failed: should not be 0\n", #field); \
+ ast_test_set_result(test, AST_TEST_FAIL); \
+ res = AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \brief Alice's Caller ID */
+#define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, }
+
+/*! \brief Bob's Caller ID */
+#define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, }
+
+/*! \brief Charlie's Caller ID */
+#define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, }
+
+/*! \brief David's Caller ID */
+#define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, }
+
+/*! \brief Copy the linkedid and uniqueid from a channel to an expected CDR */
+#define COPY_IDS(channel_var, expected_record) do { \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Alice, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_ALICE_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Bob, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_BOB_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_CHARLIE_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "300", "Charlie", "300", "300", "default", NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_DAVID_CHANNEL(channel_var, caller_id, expected_record) do { \
+ (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); \
+ ast_channel_set_caller((channel_var), (caller_id), NULL); \
+ ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+ ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+ } while (0)
+
+/*! \brief Emulate a channel entering into an application */
+#define EMULATE_APP_DATA(channel, priority, application, data) do { \
+ if ((priority) > 0) { \
+ ast_channel_priority_set((channel), (priority)); \
+ } \
+ ast_channel_appl_set((channel), (application)); \
+ ast_channel_data_set((channel), (data)); \
+ ast_channel_publish_snapshot((channel)); \
+ } while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel, cause) do { \
+ ast_channel_hangupcause_set((channel), (cause)); \
+ if (!ast_hangup((channel))) { \
+ channel = NULL; \
+ } } while (0)
+
+static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test, struct ast_cdr *expected, int record)
+{
+ struct ast_cdr *actual = NULL;
+ struct test_cdr_entry *cdr_wrapper;
+ int count = 0;
+ struct timeval wait_now = ast_tvnow();
+ struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 5, .tv_nsec = wait_now.tv_usec * 1000 };
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ while (count < record) {
+ AST_LIST_LOCK(&actual_cdr_entries);
+ if (global_mock_cdr_count < record) {
+ ast_cond_timedwait(&mock_cdr_cond, &actual_cdr_entries.lock, &wait_time);
+ }
+ cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list);
+ AST_LIST_UNLOCK(&actual_cdr_entries);
+
+ if (!cdr_wrapper) {
+ ast_test_status_update(test, "Unable to find actual CDR record at %d\n", count);
+ return AST_TEST_FAIL;
+ }
+ actual = cdr_wrapper->cdr;
+
+ if (!expected && actual) {
+ ast_test_status_update(test, "CDRs recorded where no record expected\n");
+ return AST_TEST_FAIL;
+ }
+
+ VERIFY_STRING_FIELD(accountcode, actual, expected);
+ VERIFY_NUMERIC_FIELD(amaflags, actual, expected);
+ VERIFY_STRING_FIELD(channel, actual, expected);
+ VERIFY_STRING_FIELD(clid, actual, expected);
+ VERIFY_STRING_FIELD(dcontext, actual, expected);
+ VERIFY_NUMERIC_FIELD(disposition, actual, expected);
+ VERIFY_STRING_FIELD(dst, actual, expected);
+ VERIFY_STRING_FIELD(dstchannel, actual, expected);
+ VERIFY_STRING_FIELD(lastapp, actual, expected);
+ VERIFY_STRING_FIELD(lastdata, actual, expected);
+ VERIFY_STRING_FIELD(linkedid, actual, expected);
+ VERIFY_STRING_FIELD(peeraccount, actual, expected);
+ VERIFY_STRING_FIELD(src, actual, expected);
+ VERIFY_STRING_FIELD(uniqueid, actual, expected);
+ VERIFY_STRING_FIELD(userfield, actual, expected);
+ VERIFY_TIME_VALUE(start, actual);
+ VERIFY_TIME_VALUE(end, actual);
+ /* Note: there's no way we can really calculate a duration or
+ * billsec - the unit tests are too short. However, if billsec is
+ * non-zero in the expected, then make sure we have an answer time
+ */
+ if (expected->billsec) {
+ VERIFY_TIME_VALUE(answer, actual);
+ }
+ ast_test_debug(test, "Finished expected record %s, %s\n",
+ expected->channel, S_OR(expected->dstchannel, "<none>"));
+ expected = expected->next;
+ ++count;
+ }
+ return res;
+}
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+ if (!chan) {
+ return;
+ }
+ ast_channel_release(chan);
+}
+
+AST_TEST_DEFINE(test_cdr_channel_creation)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test that a CDR is created when a channel is created";
+ info->description =
+ "Test that a CDR is created when a channel is created";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, (&caller), &expected);
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_inbound_call)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test inbound unanswered calls";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "inbound to Asterisk, executes some dialplan, but\n"
+ "is never answered.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Wait", "1");
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_outbound_call)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = {
+ .id.name.str = "",
+ .id.name.valid = 1,
+ .id.number.str = "",
+ .id.number.valid = 1, };
+ struct ast_cdr expected = {
+ .clid = "\"\" <>",
+ .dst = "s",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "AppDial",
+ .lastdata = "(Outgoing Line)",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test outbound unanswered calls";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "outbound to Asterisk but is never answered.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ ast_channel_exten_set(chan, "s");
+ ast_channel_context_set(chan, "default");
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
+ EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)");
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_party)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = "",
+ .lastapp = "VoiceMailMain",
+ .lastdata = "1",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, but only involves a single channel\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1");
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan);
+
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge_continue)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge_one, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge *, bridge_two, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &expected_two,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected_one);
+ COPY_IDS(chan, &expected_two);
+
+ EMULATE_APP_DATA(chan, 1, "Answer", "");
+ ast_setstate(chan, AST_STATE_UP);
+ EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+ bridge_one = ast_bridge_basic_new();
+ ast_test_validate(test, bridge_one != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge_one, chan, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan);
+
+ EMULATE_APP_DATA(chan, 3, "Wait", "");
+
+ /* And then it hangs up */
+ HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected_one, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_a)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &bob_expected,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. In this scenario, the\n"
+ "Party A should answer the bridge first.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_b)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &bob_expected,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. In this scenario, the\n"
+ "Party B should answer the bridge first.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_multiparty_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller_alice = ALICE_CALLERID;
+ struct ast_party_caller caller_bob = BOB_CALLERID;
+ struct ast_party_caller caller_charlie = CHARLIE_CALLERID;
+ struct ast_cdr charlie_expected = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ };
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ .peeraccount = "300",
+ .next = &charlie_expected,
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "300",
+ .next = &bob_expected,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Bridge",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering/leaving a multi-party bridge";
+ info->description =
+ "Test the properties of a CDR for a call that is\n"
+ "answered, enters a bridge, and leaves it. A total of three\n"
+ "parties perform this action.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected_one);
+ COPY_IDS(chan_alice, &alice_expected_two);
+ CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+ ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+ CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie, &charlie_expected);
+ ast_copy_string(charlie_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected.linkedid));
+
+ EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+
+ EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+ ast_setstate(chan_bob, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ EMULATE_APP_DATA(chan_charlie, 1, "Answer", "");
+ ast_setstate(chan_charlie, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_charlie, 2, "Bridge", "");
+ ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+ ast_bridge_depart(chan_charlie);
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 4);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unanswered)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that isn't answered";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation that isn't answered\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", "CDRTestChannel/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_dial_busy)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_BUSY,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in a busy";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's busy\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_congestion)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_CONGESTION,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in congestion";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's congested\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, congestion_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unavailable)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial that results in unavailable";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint that's unavailable\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_caller_cancel)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test CDRs for a dial where the caller cancels";
+ info->description =
+ "Test the properties of a CDR for a channel that\n"
+ "performs a dial operation to an endpoint but then decides\n"
+ "to hang up, cancelling the dial\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL");
+
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_parallel_failed)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_NOANSWER,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+ struct ast_cdr charlie_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_BUSY,
+ .accountcode = "100",
+ .peeraccount = "300",
+ };
+ struct ast_cdr david_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_CONGESTION,
+ .accountcode = "100",
+ .peeraccount = "400",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_cdr *expected = &bob_expected;
+ bob_expected.next = &charlie_expected;
+ charlie_expected.next = &david_expected;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test a parallel dial where all channels fail to answer";
+ info->description =
+ "This tests dialing three parties: Bob, Charlie, David. Charlie\n"
+ "returns BUSY; David returns CONGESTION; Bob fails to answer and\n"
+ "Alice hangs up. Three records are created for Alice as a result.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, congestion_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &bob_expected);
+ COPY_IDS(chan_caller, &charlie_expected);
+ COPY_IDS(chan_caller, &david_expected);
+
+ /* Channel enters Dial app */
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David");
+
+ /* Outbound channels are created */
+ chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+ chan_charlie = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "300", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Charlie");
+ ast_set_flag(ast_channel_flags(chan_charlie), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_charlie, 0, "AppDial", "(Outgoing Line)");
+
+ chan_david = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "400", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/David");
+ ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+ /* Dial starts */
+ ast_channel_publish_dial(chan_caller, chan_bob, "Bob", NULL);
+ ast_channel_publish_dial(chan_caller, chan_charlie, "Charlie", NULL);
+ ast_channel_publish_dial(chan_caller, chan_david, "David", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+
+ /* Charlie is busy */
+ ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY");
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY);
+
+ /* David is congested */
+ ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION");
+ HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION);
+
+ /* Bob is canceled */
+ ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL");
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ /* Alice hangs up */
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_no_bridge)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected_one = {
+ .clid = "\"\" <>",
+ .src = "",
+ .dst = "s",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "1",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &bob_expected_one,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and not going into a bridge.";
+ info->description =
+ "This is a weird one, but theoretically possible. You can perform\n"
+ "a dial, then bounce both channels to different priorities and\n"
+ "never have them enter a bridge together. Ew. This makes sure that\n"
+ "when we answer, we get a CDR, it gets ended at that point, and\n"
+ "that it gets finalized appropriately. We should get three CDRs in\n"
+ "the end - one for the dial, and one for each CDR as they continued\n"
+ "on.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &alice_expected_one);
+ COPY_IDS(chan_caller, &alice_expected_two);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ COPY_IDS(chan_callee, &bob_expected_one);
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_clear_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ EMULATE_APP_DATA(chan_caller, 2, "Wait", "1");
+ EMULATE_APP_DATA(chan_callee, 1, "Wait", "1");
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 3);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_a)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a 2-party bridge";
+ info->description =
+ "The most 'basic' of scenarios\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+ ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_bridge_depart(chan_caller);
+ ast_bridge_depart(chan_callee);
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_b)
+{
+ RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a 2-party bridge";
+ info->description =
+ "The most 'basic' of scenarios\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+ EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+ ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_caller, AST_STATE_UP);
+ ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_depart(chan_caller);
+ ast_bridge_depart(chan_callee);
+
+ HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &expected, 1);
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_party_caller alice_caller = ALICE_CALLERID;
+ struct ast_party_caller charlie_caller = CHARLIE_CALLERID;
+ struct ast_cdr charlie_expected_two = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/David",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ .peeraccount = "200",
+ };
+ struct ast_cdr charlie_expected_one = {
+ .clid = "\"Charlie\" <300>",
+ .src = "300",
+ .dst = "300",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Charlie",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/David",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "300",
+ .peeraccount = "400",
+ .next = &charlie_expected_two,
+ };
+ struct ast_cdr alice_expected_three = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/David",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "400",
+ .next = &charlie_expected_one,
+ };
+ struct ast_cdr alice_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Charlie",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "300",
+ .next = &alice_expected_three,
+ };
+ struct ast_cdr alice_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .dstchannel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Dial",
+ .lastdata = CHANNEL_TECH_NAME "/Bob",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .billsec = 1,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .peeraccount = "200",
+ .next = &alice_expected_two,
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test dialing, answering, and going into a multi-party bridge";
+ info->description =
+ "A little tricky to get to do, but possible with some redirects.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected_one);
+ COPY_IDS(chan_alice, &alice_expected_two);
+ COPY_IDS(chan_alice, &alice_expected_three);
+
+ EMULATE_APP_DATA(chan_alice, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+ chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob");
+ ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+ CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller, &charlie_expected_one);
+ EMULATE_APP_DATA(chan_charlie, 1, "Dial", CHANNEL_TECH_NAME "/David");
+ ast_copy_string(charlie_expected_one.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_one.uniqueid));
+ ast_copy_string(charlie_expected_one.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_one.linkedid));
+ ast_copy_string(charlie_expected_two.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_two.uniqueid));
+ ast_copy_string(charlie_expected_two.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_two.linkedid));
+
+ chan_david = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David");
+ ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+ EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+ ast_channel_publish_dial(chan_alice, chan_bob, "Bob", NULL);
+ ast_channel_state_set(chan_alice, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_charlie, chan_david, "David", NULL);
+ ast_channel_state_set(chan_charlie, AST_STATE_RINGING);
+ ast_channel_publish_dial(chan_alice, chan_bob, NULL, "ANSWER");
+ ast_channel_publish_dial(chan_charlie, chan_david, NULL, "ANSWER");
+
+ ast_channel_state_set(chan_alice, AST_STATE_UP);
+ ast_channel_state_set(chan_bob, AST_STATE_UP);
+ ast_channel_state_set(chan_charlie, AST_STATE_UP);
+ ast_channel_state_set(chan_david, AST_STATE_UP);
+
+ bridge = ast_bridge_basic_new();
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_david, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0));
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_alice));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_bob));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_charlie));
+ ast_test_validate(test, 0 == ast_bridge_depart(chan_david));
+
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected_one, 5);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_park)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller bob_caller = BOB_CALLERID;
+ struct ast_party_caller alice_caller = ALICE_CALLERID;
+ struct ast_cdr bob_expected = {
+ .clid = "\"Bob\" <200>",
+ .src = "200",
+ .dst = "200",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Bob",
+ .lastapp = "Park",
+ .lastdata = "701",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "200",
+ };
+ struct ast_cdr alice_expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Park",
+ .lastdata = "700",
+ .billsec = 1,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ .next = &bob_expected,
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test cdrs for a single party entering Park";
+ info->description =
+ "Test the properties of a CDR for calls that are\n"
+ "answered, enters Park, and leaves it.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ SWAP_CONFIG(config, debug_cdr_config);
+ CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected);
+ CREATE_BOB_CHANNEL(chan_bob, &bob_caller, &bob_expected);
+
+ EMULATE_APP_DATA(chan_alice, 1, "Park", "700");
+ ast_setstate(chan_alice, AST_STATE_UP);
+ EMULATE_APP_DATA(chan_bob, 1, "Park", "701");
+ ast_setstate(chan_bob, AST_STATE_UP);
+
+ bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING,
+ AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+ | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+ ast_test_validate(test, bridge != NULL);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_bridge_depart(chan_alice);
+ ast_bridge_depart(chan_bob);
+
+ /* And then it hangs up */
+ HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+ HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+ result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+ return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_fields)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ char varbuffer[128];
+ int int_buffer;
+ double db_buffer;
+ struct timespec to_sleep = {2, 0};
+ struct ast_flags fork_options = { 0, };
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr original = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "10",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "XXX",
+ .userfield = "yackity",
+ };
+ struct ast_cdr fork_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Wait",
+ .lastdata = "10",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "XXX",
+ .userfield = "yackity",
+ };
+ struct ast_cdr fork_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .lastapp = "Answer",
+ .billsec = 0,
+ .amaflags = AST_AMA_OMIT,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "ZZZ",
+ .userfield = "schmackity",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ struct ast_cdr *expected = &original;
+ original.next = &fork_expected_one;
+ fork_expected_one.next = &fork_expected_two;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &original);
+ ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+ ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+ ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+ ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+ /* Channel enters Wait app */
+ ast_channel_appl_set(chan, "Wait");
+ ast_channel_data_set(chan, "10");
+ ast_channel_priority_set(chan, 1);
+ ast_channel_publish_snapshot(chan);
+
+ /* Set properties on the channel that propagate to the CDR */
+ ast_channel_amaflags_set(chan, AST_AMA_OMIT);
+ ast_channel_accountcode_set(chan, "XXX");
+
+ /* Wait one second so we get a duration. */
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ ast_cdr_setuserfield(ast_channel_name(chan), "foobar");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+
+ /* Verify that we can't set read-only fields or other fields directly */
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "clid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "src", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dst", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dcontext", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "channel", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dstchannel", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastapp", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastdata", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "start", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "answer", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "end", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "duration", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "billsec", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "disposition", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "amaflags", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "accountcode", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "uniqueid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "linkedid", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "userfield", "junk") != 0);
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "sequence", "junk") != 0);
+
+ /* Verify the values */
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "userfield", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "foobar") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "amaflags", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%d", &int_buffer);
+ ast_test_validate(test, int_buffer == AST_AMA_OMIT);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "accountcode", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "XXX") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "clid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "\"Alice\" <100>") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "src", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dst", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dcontext", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "default") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "channel", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, CHANNEL_TECH_NAME "/Alice") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dstchannel", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastapp", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "Wait") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastdata", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "10") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) > 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "end", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "duration", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) > 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "billsec", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%lf", &db_buffer);
+ ast_test_validate(test, fabs(db_buffer) < EPSILON);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "disposition", varbuffer, sizeof(varbuffer)) == 0);
+ sscanf(varbuffer, "%d", &int_buffer);
+ ast_test_validate(test, int_buffer == AST_CDR_NULL);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "uniqueid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, ast_channel_uniqueid(chan)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "linkedid", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, ast_channel_linkedid(chan)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "sequence", varbuffer, sizeof(varbuffer)) == 0);
+
+ /* Fork the CDR, and check that we change the properties on both CDRs. */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Change some properties */
+ ast_cdr_setuserfield(ast_channel_name(chan), "yackity");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1b") == 0);
+
+ /* Fork the CDR again, finalizing all current CDRs */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS | AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Channel enters Answer app */
+ ast_channel_appl_set(chan, "Answer");
+ ast_channel_data_set(chan, "");
+ ast_channel_priority_set(chan, 1);
+ ast_channel_publish_snapshot(chan);
+ ast_setstate(chan, AST_STATE_UP);
+
+ /* Set properties on the last record */
+ ast_channel_accountcode_set(chan, "ZZZ");
+ ast_cdr_setuserfield(ast_channel_name(chan), "schmackity");
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+
+ /* Hang up and verify */
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_no_reset_cdr)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ struct ast_flags fork_options = { 0, };
+ struct timespec to_sleep = {1, 0};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr expected = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .billsec = 0,
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_FAILED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, unanswered_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ /* Disable the CDR */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+ /* Fork the CDR. This should be enabled */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Disable and enable the forked CDR */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+ ast_test_validate(test, ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+ /* Fork and finalize again. This CDR should be propagated */
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Disable all future CDRs */
+ ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL) == 0);
+
+ /* Fork a few more */
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, &expected, 1);
+
+ return result;
+}
+
+AST_TEST_DEFINE(test_cdr_fork_cdr)
+{
+ RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+ RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+ ao2_cleanup);
+ char varbuffer[128];
+ char fork_varbuffer[128];
+ char answer_time[128];
+ char fork_answer_time[128];
+ char start_time[128];
+ char fork_start_time[128];
+ struct ast_flags fork_options = { 0, };
+ struct timespec to_sleep = {1, 10000};
+
+ struct ast_party_caller caller = ALICE_CALLERID;
+ struct ast_cdr original = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr fork_expected_one = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ struct ast_cdr fork_expected_two = {
+ .clid = "\"Alice\" <100>",
+ .src = "100",
+ .dst = "100",
+ .dcontext = "default",
+ .channel = CHANNEL_TECH_NAME "/Alice",
+ .amaflags = AST_AMA_DOCUMENTATION,
+ .disposition = AST_CDR_ANSWERED,
+ .accountcode = "100",
+ };
+ enum ast_test_result_state result = AST_TEST_NOT_RUN;
+ struct ast_cdr *expected = &original;
+ original.next = &fork_expected_one;
+ fork_expected_one.next = &fork_expected_two;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test field access CDRs";
+ info->description =
+ "This tests setting/retrieving data on CDR records.\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ SWAP_CONFIG(config, debug_cdr_config);
+
+ CREATE_ALICE_CHANNEL(chan, &caller, &original);
+ ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+ ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+ ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+ ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+ /* Test blowing away variables */
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+ ast_copy_string(varbuffer, "", sizeof(varbuffer));
+
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_1") != 0);
+
+ /* Test finalizing previous CDRs */
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+ /* Test keep variables; setting a new answer time */
+ ast_setstate(chan, AST_STATE_UP);
+ while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+ ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+ ast_test_validate(test, strcmp(varbuffer, "record_2") == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", answer_time, sizeof(answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", start_time, sizeof(start_time)) == 0);
+
+ ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+ ast_test_validate(test, strcmp(fork_varbuffer, varbuffer) == 0);
+ ast_test_validate(test, strcmp(fork_start_time, start_time) == 0);
+ ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+ ast_clear_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+ ast_set_flag(&fork_options, AST_CDR_FLAG_RESET);
+ ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+ ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+ ast_test_validate(test, strcmp(fork_start_time, start_time) != 0);
+ ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+ if (!ast_hangup(chan)) {
+ chan = NULL;
+ }
+ result = verify_mock_cdr_record(test, expected, 3);
+
+ return result;
+}
+
+/*!
+ * \internal \brief Callback function called before each test executes
+ */
+static int test_cdr_init_cb(struct ast_test_info *info, struct ast_test *test)
+{
+ /* Back up the real config */
+ saved_config = ast_cdr_get_config();
+ clear_mock_cdr_backend();
+ return 0;
+}
+
+/*!
+ * \internal \brief Callback function called after each test executes
+ */
+static int test_cdr_cleanup_cb(struct ast_test_info *info, struct ast_test *test)
+{
+ /* Restore the real config */
+ ast_cdr_set_config(saved_config);
+ ao2_cleanup(saved_config);
+ saved_config = NULL;
+ clear_mock_cdr_backend();
+
+ return 0;
+}
+
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(test_cdr_channel_creation);
+ AST_TEST_UNREGISTER(test_cdr_unanswered_inbound_call);
+ AST_TEST_UNREGISTER(test_cdr_unanswered_outbound_call);
+ AST_TEST_UNREGISTER(test_cdr_single_party);
+ AST_TEST_UNREGISTER(test_cdr_single_bridge);
+ AST_TEST_UNREGISTER(test_cdr_single_bridge_continue);
+ AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_a);
+ AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_b);
+ AST_TEST_UNREGISTER(test_cdr_single_multiparty_bridge);
+
+ AST_TEST_UNREGISTER(test_cdr_dial_unanswered);
+ AST_TEST_UNREGISTER(test_cdr_dial_congestion);
+ AST_TEST_UNREGISTER(test_cdr_dial_busy);
+ AST_TEST_UNREGISTER(test_cdr_dial_unavailable);
+ AST_TEST_UNREGISTER(test_cdr_dial_caller_cancel);
+ AST_TEST_UNREGISTER(test_cdr_dial_parallel_failed);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_no_bridge);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+ AST_TEST_UNREGISTER(test_cdr_dial_answer_multiparty);
+
+ AST_TEST_UNREGISTER(test_cdr_park);
+
+ AST_TEST_UNREGISTER(test_cdr_fields);
+ AST_TEST_UNREGISTER(test_cdr_no_reset_cdr);
+ AST_TEST_UNREGISTER(test_cdr_fork_cdr);
+
+ ast_cdr_unregister(MOCK_CDR_BACKEND);
+ ast_channel_unregister(&test_cdr_chan_tech);
+ clear_mock_cdr_backend();
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_cond_init(&mock_cdr_cond, NULL);
+
+ AST_TEST_REGISTER(test_cdr_channel_creation);
+ AST_TEST_REGISTER(test_cdr_unanswered_inbound_call);
+ AST_TEST_REGISTER(test_cdr_unanswered_outbound_call);
+
+ AST_TEST_REGISTER(test_cdr_single_party);
+ AST_TEST_REGISTER(test_cdr_single_bridge);
+ AST_TEST_REGISTER(test_cdr_single_bridge_continue);
+ AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_a);
+ AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_b);
+ AST_TEST_REGISTER(test_cdr_single_multiparty_bridge);
+
+ AST_TEST_REGISTER(test_cdr_dial_unanswered);
+ AST_TEST_REGISTER(test_cdr_dial_congestion);
+ AST_TEST_REGISTER(test_cdr_dial_busy);
+ AST_TEST_REGISTER(test_cdr_dial_unavailable);
+ AST_TEST_REGISTER(test_cdr_dial_caller_cancel);
+ AST_TEST_REGISTER(test_cdr_dial_parallel_failed);
+ AST_TEST_REGISTER(test_cdr_dial_answer_no_bridge);
+ AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+ AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+ AST_TEST_REGISTER(test_cdr_dial_answer_multiparty);
+
+ AST_TEST_REGISTER(test_cdr_park);
+
+ AST_TEST_REGISTER(test_cdr_fields);
+ AST_TEST_REGISTER(test_cdr_no_reset_cdr);
+ AST_TEST_REGISTER(test_cdr_fork_cdr);
+
+ ast_test_register_init(TEST_CATEGORY, test_cdr_init_cb);
+ ast_test_register_cleanup(TEST_CATEGORY, test_cdr_cleanup_cb);
+
+ ast_channel_register(&test_cdr_chan_tech);
+ ast_cdr_register(MOCK_CDR_BACKEND, "Mock CDR backend", mock_cdr_backend_cb);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "CDR unit tests");