Newer
Older
/* CDR destruction used to work by calling ao2_cleanup(next) and
* allowing the chain to destroy itself neatly. Unfortunately, for
* really long chains, this can result in a stack overflow. So now
* when the root CDR is destroyed, it is responsible for unreffing
* all CDRs in the chain
*/
if (cdr->is_root) {
struct cdr_object *curr = cdr->next;
struct cdr_object *next;
while (curr) {
next = curr->next;
ao2_cleanup(curr);
curr = next;
}
}
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
}
/*!
* \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 cdr_object *cdr;
ast_assert(chan != NULL);
cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor);
if (!cdr) {
return NULL;
}
cdr->last = cdr;
if (ast_string_field_init(cdr, 64)) {
return NULL;
}
ast_string_field_set(cdr, uniqueid, chan->uniqueid);
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);
cdr->party_a.snapshot = chan;
ao2_t_ref(cdr->party_a.snapshot, +1, "bump snapshot during CDR creation");
CDR_DEBUG("%p - Created CDR for channel %s\n", cdr, chan->name);
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
cdr_object_transition_state(cdr, &single_state_fn_table);
return cdr;
}
/*!
* \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)
{
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;
/* 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);
Matt Jordan
committed
ast_string_field_set(new_cdr, context, cdr_last->context);
ast_string_field_set(new_cdr, exten, cdr_last->exten);
/*
* If the current CDR says to disable all future ones,
* keep the disable chain going
*/
if (ast_test_flag(&cdr_last->flags, AST_CDR_FLAG_DISABLE_ALL)) {
ast_set_flag(&new_cdr->flags, AST_CDR_FLAG_DISABLE_ALL);
}
/* 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 new_cdr;
}
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
/*!
* \internal
* \brief Determine if CDR flag is configured.
*
* \param cdr_flag The configured CDR flag to check.
*
* \retval 0 if the CDR flag is not configured.
* \retval non-zero if the CDR flag is configured.
*
* \return Nothing
*/
static int is_cdr_flag_set(unsigned int cdr_flag)
{
struct module_config *mod_cfg;
int flag_set;
mod_cfg = ao2_global_obj_ref(module_configs);
flag_set = mod_cfg && ast_test_flag(&mod_cfg->general->settings, cdr_flag);
ao2_cleanup(mod_cfg);
return flag_set;
}
Matthew Jordan
committed
/*!
* \brief Return whether or not a channel has changed its state in the dialplan, subject
* to endbeforehexten logic
*
* \param old_snapshot The previous state
* \param new_snapshot The new state
*
* \retval 0 if the state has not changed
* \retval 1 if the state changed
*/
static int snapshot_cep_changed(struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot)
{
/* If we ignore hangup logic, don't indicate that we're executing anything new */
if (ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
&& is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
Matthew Jordan
committed
return 0;
}
/* 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". Ignore application changes to AppDialX as a result.
*/
if (strcmp(new_snapshot->appl, old_snapshot->appl)
&& strncasecmp(new_snapshot->appl, "appdial", 7)
&& (strcmp(new_snapshot->context, old_snapshot->context)
|| strcmp(new_snapshot->exten, old_snapshot->exten)
|| new_snapshot->priority != old_snapshot->priority)) {
Matthew Jordan
committed
return 1;
}
return 0;
}
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
/*!
* \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)
{
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) {
} else {
/* Okay, fine, take the left one */
return left;
}
}
/*!
* Compute the duration for a \ref cdr_object
*/
static long cdr_object_get_duration(struct cdr_object *cdr)
{
Matthew Jordan
committed
return (long)(ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->start) / 1000);
}
/*!
* \brief Compute the billsec for a \ref cdr_object
*/
static long cdr_object_get_billsec(struct cdr_object *cdr)
{
long int ms;
if (ast_tvzero(cdr->answer)) {
return 0;
}
Matthew Jordan
committed
ms = ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->answer);
if (ms % 1000 >= 500
&& is_cdr_flag_set(CDR_INITIATED_SECONDS)) {
ms = (ms / 1000) + 1;
} else {
ms = ms / 1000;
}
return ms;
}
Matthew Jordan
committed
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
/*!
* \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
*/
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))) {
Matthew Jordan
committed
AST_LIST_INSERT_HEAD(headp, newvariable, entries);
}
}
/*!
* \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)
{
Joshua Colp
committed
struct ast_cdr *pub_cdr = NULL, *cdr_prev = NULL;
Matthew Jordan
committed
struct cdr_object *it_cdr;
struct ast_var_t *it_var, *it_copy_var;
struct ast_channel_snapshot *party_a;
struct ast_channel_snapshot *party_b;
Matthew Jordan
committed
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
struct ast_cdr *cdr_copy;
/* Don't create records for CDRs where the party A was a dialed channel */
if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
Matthew Jordan
committed
ast_debug(1, "CDR for %s is dialed and has no Party B; discarding\n",
continue;
}
cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
if (!cdr_copy) {
ast_free(pub_cdr);
return NULL;
}
Matthew Jordan
committed
party_a = it_cdr->party_a.snapshot;
party_b = it_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));
Matthew Jordan
committed
ast_copy_string(cdr_copy->lastapp, it_cdr->appl, sizeof(cdr_copy->lastapp));
ast_copy_string(cdr_copy->lastdata, it_cdr->data, sizeof(cdr_copy->lastdata));
ast_copy_string(cdr_copy->dst, it_cdr->exten, sizeof(cdr_copy->dst));
ast_copy_string(cdr_copy->dcontext, it_cdr->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));
Matthew Jordan
committed
if (!ast_strlen_zero(it_cdr->party_b.userfield)) {
snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", it_cdr->party_a.userfield, it_cdr->party_b.userfield);
Matthew Jordan
committed
if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(it_cdr->party_a.userfield)) {
ast_copy_string(cdr_copy->userfield, it_cdr->party_a.userfield, sizeof(cdr_copy->userfield));
}
/* Timestamps/durations */
Matthew Jordan
committed
cdr_copy->start = it_cdr->start;
cdr_copy->answer = it_cdr->answer;
cdr_copy->end = it_cdr->end;
cdr_copy->billsec = cdr_object_get_billsec(it_cdr);
cdr_copy->duration = cdr_object_get_duration(it_cdr);
/* Flags and IDs */
Matthew Jordan
committed
ast_copy_flags(cdr_copy, &it_cdr->flags, AST_FLAGS_ALL);
ast_copy_string(cdr_copy->linkedid, it_cdr->linkedid, sizeof(cdr_copy->linkedid));
cdr_copy->disposition = it_cdr->disposition;
cdr_copy->sequence = it_cdr->sequence;
/* Variables */
Matthew Jordan
committed
copy_variables(&cdr_copy->varshead, &it_cdr->party_a.variables);
AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
struct ast_var_t *newvariable;
AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
if (!strcasecmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
found = 1;
break;
}
}
if (!found && (newvariable = ast_var_assign(ast_var_name(it_var), ast_var_value(it_var)))) {
AST_LIST_INSERT_TAIL(&cdr_copy->varshead, newvariable, entries);
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
}
}
if (!pub_cdr) {
pub_cdr = cdr_copy;
cdr_prev = pub_cdr;
} else {
cdr_prev->next = cdr_copy;
cdr_prev = cdr_copy;
}
}
return pub_cdr;
}
/*!
* \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_cdr *pub_cdr;
CDR_DEBUG("%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)
{
/* 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 (!is_cdr_flag_set(CDR_CONGESTION)) {
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
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;
}
}
/*!
* \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)
{
if (!ast_tvzero(cdr->end)) {
return;
}
cdr->end = ast_tvnow();
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;
}
}
/* tv_usec is suseconds_t, which could be int or long */
ast_debug(1, "Finalized CDR for %s - start %ld.%06ld answer %ld.%06ld end %ld.%06ld dispo %s\n",
cdr->party_a.snapshot->name,
(long)cdr->start.tv_sec,
(long)cdr->start.tv_usec,
(long)cdr->answer.tv_sec,
(long)cdr->answer.tv_usec,
(long)cdr->end.tv_sec,
(long)cdr->end.tv_usec,
ast_cdr_disp2str(cdr->disposition));
}
/*!
* \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->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
&& is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
Matthew Jordan
committed
cdr_object_finalize(cdr);
}
if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_DEAD)
&& 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)
{
if (cdr->party_a.snapshot->state == AST_STATE_UP && ast_tvzero(cdr->answer)) {
cdr->answer = ast_tvnow();
/* tv_usec is suseconds_t, which could be int or long */
CDR_DEBUG("%p - Set answered time to %ld.%06ld\n", cdr,
(long)cdr->answer.tv_sec,
(long)cdr->answer.tv_usec);
}
}
/* \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);
ao2_t_replace(old_snapshot->snapshot, new_snapshot, "Swap CDR shapshot");
}
/* BASE METHOD IMPLEMENTATIONS */
static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
ast_assert(strcasecmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
Matthew Jordan
committed
/* Finalize the CDR if we're in hangup logic and we're set to do so */
Matthew Jordan
committed
if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
&& is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
cdr_object_finalize(cdr);
Matthew Jordan
committed
return 0;
}
/*
* Only record the context and extension if we aren't in a subroutine, or if
* we are executing hangup logic.
*/
if (!ast_test_flag(&snapshot->flags, AST_FLAG_SUBROUTINE_EXEC)
|| ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
if (strcmp(cdr->context, snapshot->context)) {
ast_string_field_set(cdr, context, snapshot->context);
}
if (strcmp(cdr->exten, snapshot->exten)) {
ast_string_field_set(cdr, exten, snapshot->exten);
}
}
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_test_flag(&cdr->flags, AST_CDR_LOCK_APP)
&& !ast_strlen_zero(snapshot->appl)
&& (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) {
if (strcmp(cdr->appl, snapshot->appl)) {
ast_string_field_set(cdr, appl, snapshot->appl);
}
if (strcmp(cdr->data, snapshot->data)) {
ast_string_field_set(cdr, data, snapshot->data);
}
Matthew Jordan
committed
/* Dial (app_dial) is a special case. Because pre-dial handlers, which
* execute before the dial begins, will alter the application/data to
* something people typically don't want to see, if we see a channel enter
* into Dial here, we set the appl/data accordingly and lock it.
*/
if (!strcmp(snapshot->appl, "Dial")) {
ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
}
if (strcmp(cdr->linkedid, snapshot->linkedid)) {
ast_string_field_set(cdr, linkedid, snapshot->linkedid);
}
cdr_object_check_party_a_answer(cdr);
cdr_object_check_party_a_hangup(cdr);
return 0;
}
static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
/* In general, most things shouldn't get a bridge leave */
ast_assert(0);
return 1;
}
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;
}
Matthew Jordan
committed
static enum process_bridge_enter_results base_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
/* Base process bridge enter simply indicates that we can't handle it */
return BRIDGE_ENTER_NEED_CDR;
}
static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info)
{
char park_info[128];
ast_assert(!strcasecmp(parking_info->parkee->name, cdr->party_a.snapshot->name));
/* Update Party A information regardless */
cdr->fn_table->process_party_a(cdr, parking_info->parkee);
/* Fake out where we're parked */
ast_string_field_set(cdr, appl, "Park");
snprintf(park_info, sizeof(park_info), "%s:%u", parking_info->parkinglot, parking_info->parkingspace);
ast_string_field_set(cdr, data, park_info);
/* Prevent any further changes to the App/Data fields for this record */
ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
return 0;
}
/* SINGLE STATE */
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)
{
if (caller && !strcasecmp(cdr->party_a.snapshot->name, caller->name)) {
base_process_party_a(cdr, caller);
CDR_DEBUG("%p - Updated Party A %s snapshot\n", cdr,
cdr->party_a.snapshot->name);
cdr_object_swap_snapshot(&cdr->party_b, peer);
cdr_all_relink(cdr);
CDR_DEBUG("%p - Updated Party B %s snapshot\n", cdr,
cdr->party_b.snapshot->name);
Matthew Jordan
committed
/* If we have two parties, lock the application that caused the
* two parties to be associated. This prevents mid-call event
* macros/gosubs from perturbing the CDR application/data
*/
ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
} else if (!strcasecmp(cdr->party_a.snapshot->name, peer->name)) {
/* We're the entity being dialed, i.e., outbound origination */
base_process_party_a(cdr, peer);
CDR_DEBUG("%p - Updated Party A %s snapshot\n", cdr,
cdr->party_a.snapshot->name);
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
}
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;
/* Don't match on ourselves */
if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)) {
return 1;
}
/* Try the candidate CDR's Party A first */
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
CDR_DEBUG("%p - Party A %s has new Party B %s\n",
cdr, cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name);
cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
cdr_all_relink(cdr);
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;
}
/* Try their Party B, unless it's us */
if (!cand_cdr->party_b.snapshot
|| !strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name)) {
return 1;
}
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
CDR_DEBUG("%p - Party A %s has new Party B %s\n",
cdr, cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name);
cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
cdr_all_relink(cdr);
return 0;
}
return 1;
}
Matthew Jordan
committed
static enum process_bridge_enter_results single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
struct ao2_iterator it_cdrs;
char *channel_id;
Matthew Jordan
committed
int success = 0;
ast_string_field_set(cdr, bridge, bridge->uniqueid);
if (ao2_container_count(bridge->channels) == 1) {
/* No one in the bridge yet but us! */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
Matthew Jordan
committed
return BRIDGE_ENTER_ONLY_PARTY;
for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
!success && (channel_id = ao2_iterator_next(&it_cdrs));
ao2_ref(channel_id, -1)) {
struct cdr_object *cand_cdr_master;
struct cdr_object *cand_cdr;
cand_cdr_master = ao2_find(active_cdrs_master, channel_id, OBJ_SEARCH_KEY);
if (!cand_cdr_master) {
continue;
}
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 (single_state_bridge_enter_comparison(cdr, cand_cdr)) {
continue;
}
/* We successfully got a party B - break out */
Matthew Jordan
committed
success = 1;
ao2_cleanup(cand_cdr_master);
/* 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 */
Matthew Jordan
committed
if (success) {
return BRIDGE_ENTER_OBTAINED_PARTY_B;
}
return BRIDGE_ENTER_NO_PARTY_B;
static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
cdr_object_transition_state(cdr, &parked_state_fn_table);
return 0;
}
/* DIAL STATE */
static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
ast_assert(snapshot != NULL);
ast_assert(cdr->party_b.snapshot
&& !strcasecmp(cdr->party_b.snapshot->name, snapshot->name));
cdr_object_swap_snapshot(&cdr->party_b, snapshot);
/* If party B hangs up, finalize this CDR */
Matthew Jordan
committed
if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
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)
{
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 (!is_cdr_flag_set(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;
}
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)
{
struct ast_channel_snapshot *party_a;
if (caller) {
party_a = caller;
} else {
party_a = peer;
}
ast_assert(!strcasecmp(cdr->party_a.snapshot->name, party_a->name));
cdr_object_swap_snapshot(&cdr->party_a, party_a);
if (cdr->party_b.snapshot) {
if (strcasecmp(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;
}
Matthew Jordan
committed
static enum process_bridge_enter_results dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
Matthew Jordan
committed
int success = 0;
ast_string_field_set(cdr, bridge, bridge->uniqueid);
/* Get parties in the bridge */
if (ao2_container_count(bridge->channels) == 1) {
/* No one in the bridge yet but us! */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
Matthew Jordan
committed
return BRIDGE_ENTER_ONLY_PARTY;
/* If we don't have a Party B (originated channel), skip it */
if (cdr->party_b.snapshot) {
struct ao2_iterator it_cdrs;
char *channel_id;
for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
!success && (channel_id = ao2_iterator_next(&it_cdrs));
ao2_ref(channel_id, -1)) {
struct cdr_object *cand_cdr_master;
struct cdr_object *cand_cdr;
cand_cdr_master = ao2_find(active_cdrs_master, channel_id, OBJ_SEARCH_KEY);
if (!cand_cdr_master) {
continue;
}
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;
}
/* Skip any records that aren't our Party B */
if (strcasecmp(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 = 1;
break;
ao2_unlock(cand_cdr_master);
ao2_cleanup(cand_cdr_master);
ao2_iterator_destroy(&it_cdrs);
}
/* 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 */
Matthew Jordan
committed
if (success) {
return BRIDGE_ENTER_OBTAINED_PARTY_B;
}
return BRIDGE_ENTER_NO_PARTY_B;
}
/* DIALED PENDING STATE */
static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
/* 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.
*/
Matthew Jordan
committed
if (snapshot_cep_changed(cdr->party_a.snapshot, snapshot)) {
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;
}
Matthew Jordan
committed
static enum process_bridge_enter_results dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
cdr_object_transition_state(cdr, &dial_state_fn_table);
return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
}
static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
Matthew Jordan
committed
if (cdr->party_b.snapshot) {
/* We can't handle this as we have a Party B - ask for a new one */
return 1;
}
cdr_object_transition_state(cdr, &parked_state_fn_table);
return 0;
static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
{
cdr_object_transition_state(cdr, &finalized_state_fn_table);
/* Ask for a new CDR */
return 1;
}
/* BRIDGE STATE */
static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
{
ast_assert(cdr->party_b.snapshot
&& !strcasecmp(cdr->party_b.snapshot->name, snapshot->name));
cdr_object_swap_snapshot(&cdr->party_b, snapshot);
/* If party B hangs up, finalize this CDR */
Matthew Jordan
committed
if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
cdr_object_transition_state(cdr, &finalized_state_fn_table);
}
}
static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
if (strcmp(cdr->bridge, bridge->uniqueid)) {