Skip to content
Snippets Groups Projects
cdr.c 140 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	/* 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;
    		}
    	}
    
    }
    
    /*!
     * \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)) {
    
    		ao2_cleanup(cdr);
    
    	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);
    
    
    	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);
    
    	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;
    }
    
    
    /*!
     * \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;
    }
    
    
    /*!
     * \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)) {
    
    	/* 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)) {
    
    /*!
     * \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) {
    
    		return right;
    
    	} 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)
    {
    
    	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;
    	}
    
    	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;
    }
    
    
    /*!
     * \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))) {
    
    /*!
     * \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 = NULL;
    
    	struct ast_var_t *it_var, *it_copy_var;
    	struct ast_channel_snapshot *party_a;
    	struct ast_channel_snapshot *party_b;
    
    
    	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) {
    
    			ast_debug(1, "CDR for %s is dialed and has no Party B; discarding\n",
    
    				it_cdr->party_a.snapshot->name);
    
    			continue;
    		}
    
    		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
    		if (!cdr_copy) {
    			ast_free(pub_cdr);
    			return NULL;
    		}
    
    
    		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));
    
    		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));
    
    			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);
    
    		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));
    
    		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);
    
    		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;
    
    		copy_variables(&cdr_copy->varshead, &it_cdr->party_a.variables);
    		AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
    
    			AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
    
    				if (!strcasecmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
    
    			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);
    
    			}
    		}
    
    		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)) {
    
    			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)) {
    
    		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);
    
    	/* Finalize the CDR if we're in hangup logic and we're set to do so */
    
    	if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
    
    		&& is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
    
    
    	/*
    	 * 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);
    		}
    
    
    		/* 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;
    }
    
    
    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;
    }
    
    
    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_DEBUG("%p - Updated Party B %s snapshot\n", cdr,
    			cdr->party_b.snapshot->name);
    
    
    		/* 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 */
    
    		CDR_DEBUG("%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;
    
    
    	/* Don't match on ourselves */
    
    	if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)) {
    
    	/* 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);
    
    		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);
    
    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;
    
    
    	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);
    
    	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 */
    
    		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 */
    
    	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 */
    
    	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;
    }
    
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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;
    }
    
    
    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)
    
    
    	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);
    
    	/* 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);
    
    			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 */
    
    	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.
    	 */
    
    	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;
    }
    
    
    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)
    
    	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 */
    
    	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)) {