summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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");