Skip to content
Snippets Groups Projects
chan_misdn.c 385 KiB
Newer Older
  • Learn to ignore specific revisions
  • Russell Bryant's avatar
    Russell Bryant committed
     * Asterisk -- An open source telephony toolkit.
    
     * Copyright (C) 2004 - 2006, Christian Richter
    
     *
     * Christian Richter <crich@beronet.com>
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * 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
    
    Russell Bryant's avatar
    Russell Bryant committed
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     *
     */
    
    /*!
     * \file
     *
     * \brief the chan_misdn channel driver for Asterisk
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \author Christian Richter <crich@beronet.com>
     *
    
     * MISDN http://www.misdn.org/
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \ingroup channel_drivers
    
    /*! \li \ref chan_misdn.c uses the configuration file \ref misdn.conf
    
    Andrew Latham's avatar
    Andrew Latham committed
     * \addtogroup configuration_file
     */
    
    /*! \page misdn.conf misdn.conf
     * \verbinclude misdn.conf.sample
     */
    
    
    /*!
     * \note
     * To use the CCBS/CCNR supplementary service feature and other
     * supplementary services using FACILITY messages requires a
     * modified version of mISDN.
     *
     * \note
     * The latest modified mISDN v1.1.x based version is available at:
     * http://svn.digium.com/svn/thirdparty/mISDN/trunk
     * http://svn.digium.com/svn/thirdparty/mISDNuser/trunk
     *
     * \note
     * Taged versions of the modified mISDN code are available under:
     * http://svn.digium.com/svn/thirdparty/mISDN/tags
     * http://svn.digium.com/svn/thirdparty/mISDNuser/tags
     */
    
    /* Define to enable cli commands to generate canned CCBS messages. */
    // #define CCBS_TEST_MESSAGES	1
    
    
    /*
     * XXX The mISDN channel driver needs its native bridge code
     * converted to the new bridge technology scheme.  The
     * chan_dahdi native bridge code can be used as an example.  It
     * is unlikely that this will ever get done.  Support for this
     * channel driver is dwindling because the supported version of
     * mISDN does not support newer kernels.
     *
     * Without native bridge support, the following config file
     * parameters have no effect: bridging.
     *
     * The existing native bridge code is marked with the
     * mISDN_NATIVE_BRIDGING conditional.
     */
    
    
    /*** MODULEINFO
    	<depend>isdnnet</depend>
    	<depend>misdn</depend>
    	<depend>suppserv</depend>
    
    	<support_level>extended</support_level>
    
    ASTERISK_REGISTER_FILE()
    
    #include <pthread.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    
    #include <semaphore.h>
    
    #include "asterisk/channel.h"
    #include "asterisk/config.h"
    #include "asterisk/module.h"
    #include "asterisk/pbx.h"
    #include "asterisk/io.h"
    #include "asterisk/frame.h"
    #include "asterisk/translate.h"
    #include "asterisk/cli.h"
    #include "asterisk/musiconhold.h"
    #include "asterisk/dsp.h"
    #include "asterisk/file.h"
    #include "asterisk/callerid.h"
    #include "asterisk/indications.h"
    #include "asterisk/app.h"
    #include "asterisk/features.h"
    
    #include "asterisk/abstract_jb.h"
    
    #include "asterisk/causes.h"
    
    #include "asterisk/format.h"
    #include "asterisk/format_cap.h"
    
    #include "asterisk/features_config.h"
    
    #include "asterisk/bridge.h"
    
    #include "chan_misdn_config.h"
    #include "isdn_lib.h"
    
    static char global_tracefile[BUFFERSIZE + 1];
    
    
    struct misdn_jb{
    	int size;
    	int upper_threshold;
    	char *samples, *ok;
    	int wp,rp;
    	int state_empty;
    	int state_full;
    	int state_buffer;
    	int bytes_wrote;
    	ast_mutex_t mutexjb;
    };
    
    
    /*! \brief allocates the jb-structure and initialize the elements */
    
    struct misdn_jb *misdn_jb_init(int size, int upper_threshold);
    
    
    /*! \brief frees the data and destroys the given jitterbuffer struct */
    
    /*! \brief fills the jitterbuffer with len data returns < 0 if there was an
    
    error (buffer overrun). */
    
    int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len);
    
    
    /*! \brief gets len bytes out of the jitterbuffer if available, else only the
    
    available data is returned and the return value indicates the number
    of data. */
    int misdn_jb_empty(struct misdn_jb *jb, char *data, int len);
    
    
    Jason Parker's avatar
    Jason Parker committed
    static char *complete_ch(struct ast_cli_args *a);
    static char *complete_debug_port(struct ast_cli_args *a);
    static char *complete_show_config(struct ast_cli_args *a);
    
    /* BEGIN: chan_misdn.h */
    
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    /*
     * This timeout duration is to clean up any call completion records that
     * are forgotten about by the switch.
     */
    #define MISDN_CC_RECORD_AGE_MAX		(6UL * 60 * 60)	/* seconds */
    
    #define MISDN_CC_REQUEST_WAIT_MAX	5	/* seconds */
    
    /*!
     * \brief Caller that initialized call completion services
     *
     * \details
     * This data is the payload for a datastore that is put on the channel that
     * initializes call completion services.  This datastore is set to be inherited
     * by the outbound mISDN channel.  When one of these channels hangs up, the
     * channel pointer will be set to NULL.  That way, we can ensure that we do not
     * touch this channel after it gets destroyed.
     */
    struct misdn_cc_caller {
    	/*! \brief The channel that initialized call completion services */
    	struct ast_channel *chan;
    };
    
    struct misdn_cc_notify {
    	/*! \brief Dialplan: Notify extension priority */
    	int priority;
    
    	/*! \brief Dialplan: Notify extension context */
    	char context[AST_MAX_CONTEXT];
    
    	/*! \brief Dialplan: Notify extension number (User-A) */
    	char exten[AST_MAX_EXTENSION];
    };
    
    /*! \brief mISDN call completion record */
    struct misdn_cc_record {
    	/*! \brief Call completion record linked list */
    	AST_LIST_ENTRY(misdn_cc_record) list;
    
    	/*! \brief Time the record was created. */
    	time_t time_created;
    
    	/*! \brief MISDN_CC_RECORD_ID value */
    	long record_id;
    
    	/*!
    	 * \brief Logical Layer 1 port associated with this
    	 * call completion record
    	 */
    	int port;
    
    	/*! \brief TRUE if point-to-point mode (CCBS-T/CCNR-T mode) */
    	int ptp;
    
    	/*! \brief Mode specific parameters */
    	union {
    		/*! \brief point-to-point specific parameters. */
    		struct {
    			/*!
    			 * \brief Call-completion signaling link.
    			 * NULL if signaling link not established.
    			 */
    			struct misdn_bchannel *bc;
    
    			/*!
    			 * \brief TRUE if we requested the request retention option
    			 * to be enabled.
    			 */
    			int requested_retention;
    
    			/*!
    			 * \brief TRUE if the request retention option is enabled.
    			 */
    			int retention_enabled;
    		} ptp;
    
    		/*! \brief point-to-multi-point specific parameters. */
    		struct {
    			/*! \brief CallLinkageID (valid when port determined) */
    			int linkage_id;
    
    			/*! \breif CCBSReference (valid when activated is TRUE) */
    			int reference_id;
    
    			/*! \brief globalRecall(0),	specificRecall(1) */
    			int recall_mode;
    		} ptmp;
    	} mode;
    
    	/*! \brief TRUE if call completion activated */
    	int activated;
    
    	/*! \brief Outstanding message ID (valid when outstanding_message) */
    	int invoke_id;
    
    	/*! \brief TRUE if waiting for a response from a message (invoke_id is valid) */
    	int outstanding_message;
    
    	/*! \brief TRUE if activation has been requested */
    	int activation_requested;
    
    	/*!
    	 * \brief TRUE if User-A is free
    	 * \note PTMP - Used to answer CCBSStatusRequest.
    	 * PTP - Determines how to respond to CCBS_T_RemoteUserFree.
    	 */
    	int party_a_free;
    
    	/*! \brief Error code received from last outstanding message. */
    	enum FacErrorCode error_code;
    
    	/*! \brief Reject code received from last outstanding message. */
    	enum FacRejectCode reject_code;
    
    	/*!
    	 * \brief Saved struct misdn_bchannel call information when
    	 * attempted to call User-B
    	 */
    	struct {
    		/*! \brief User-A caller id information */
    		struct misdn_party_id caller;
    
    		/*! \brief User-B number information */
    		struct misdn_party_dialing dialed;
    
    		/*! \brief The BC, HLC (optional) and LLC (optional) contents from the SETUP message. */
    		struct Q931_Bc_Hlc_Llc setup_bc_hlc_llc;
    
    		/*! \brief SETUP message bearer capability field code value */
    		int capability;
    
    		/*! \brief TRUE if call made in digital HDLC mode */
    		int hdlc;
    	} redial;
    
    	/*! \brief Dialplan location to indicate User-B free and User-A is free */
    	struct misdn_cc_notify remote_user_free;
    
    	/*! \brief Dialplan location to indicate User-B free and User-A is busy */
    	struct misdn_cc_notify b_free;
    };
    
    /*! \brief mISDN call completion record database */
    static AST_LIST_HEAD_STATIC(misdn_cc_records_db, misdn_cc_record);
    /*! \brief Next call completion record ID to use */
    static __u16 misdn_cc_record_id;
    /*! \brief Next invoke ID to use */
    static __s16 misdn_invoke_id;
    
    static const char misdn_no_response_from_network[] = "No response from network";
    static const char misdn_cc_record_not_found[] = "Call completion record not found";
    
    /* mISDN channel variable names */
    #define MISDN_CC_RECORD_ID	"MISDN_CC_RECORD_ID"
    #define MISDN_CC_STATUS		"MISDN_CC_STATUS"
    #define MISDN_ERROR_MSG		"MISDN_ERROR_MSG"
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    
    static ast_mutex_t release_lock;
    
    enum misdn_chan_state {
    
    	MISDN_NOTHING = 0,         /*!< at beginning */
    	MISDN_WAITING4DIGS,        /*!< when waiting for info */
    	MISDN_EXTCANTMATCH,        /*!< when asterisk couldn't match our ext */
    	MISDN_INCOMING_SETUP,      /*!< for incoming setup */
    	MISDN_DIALING,             /*!< when pbx_start */
    	MISDN_PROGRESS,            /*!< we have progress */
    	MISDN_PROCEEDING,          /*!< we have progress */
    	MISDN_CALLING,             /*!< when misdn_call is called */
    	MISDN_CALLING_ACKNOWLEDGE, /*!< when we get SETUP_ACK */
    	MISDN_ALERTING,            /*!< when Alerting */
    	MISDN_BUSY,                /*!< when BUSY */
    	MISDN_CONNECTED,           /*!< when connected */
    	MISDN_DISCONNECTED,        /*!< when connected */
    	MISDN_CLEANING,            /*!< when hangup from * but we were connected before */
    
    /*! Asterisk created the channel (outgoing call) */
    
    /*! mISDN created the channel (incoming call) */
    
    enum misdn_hold_state {
    	MISDN_HOLD_IDLE,		/*!< HOLD not active */
    	MISDN_HOLD_ACTIVE,		/*!< Call is held */
    	MISDN_HOLD_TRANSFER,	/*!< Held call is being transferred */
    	MISDN_HOLD_DISCONNECT,	/*!< Held call is being disconnected */
    };
    
    struct hold_info {
    
    	 * \brief Call HOLD state.
    	 */
    	enum misdn_hold_state state;
    	/*!
    	 * \brief Logical port the channel call record is HELD on
    
    	 * because the B channel is no longer associated.
    
    	 * \brief Original B channel number the HELD call was using.
    
    	 * \note Used only for debug display messages.
    	 */
    
    #define chan_list_ref(obj, debug) (ao2_t_ref((obj), +1, (debug)), (obj))
    #define chan_list_unref(obj, debug) (ao2_t_ref((obj), -1, (debug)), NULL)
    
    
    /*!
     * \brief Channel call record structure
     */
    
    	 * \brief The "allowed_bearers" string read in from /etc/asterisk/misdn.conf
    	 */
    
    	 * \brief State of the channel
    	 */
    
    	enum misdn_chan_state state;
    
    	/*!
    	 * \brief TRUE if a hangup needs to be queued
    
    	 * \note This is a debug flag only used to catch calls to hangup_chan() that are already hungup.
    	 */
    
    
    	/*!
    	 * \brief TRUE if a channel can be hung up by calling asterisk directly when done.
    	 */
    
    
    	/*!
    	 * \brief TRUE if we could send an AST_CONTROL_BUSY if needed.
    	 */
    
    	/*!
    	 * \brief Who originally created this channel. ORG_AST or ORG_MISDN
    	 */
    
    	 * \brief TRUE of we are not to respond immediately to a SETUP message.  Check the dialplan first.
    	 * \note The "noautorespond_on_setup" boolean read in from /etc/asterisk/misdn.conf
    	 */
    
    	int noautorespond_on_setup;
    
    	int norxtone;	/*!< Boolean assigned values but the value is not used. */
    
    
    	/*!
    	 * \brief TRUE if we are not to generate tones (Playtones)
    	 */
    
    	/*!
    	 * \brief TRUE if echo canceller is enabled.  Value is toggled.
    	 */
    
    	/*!
    	 * \brief TRUE if you want to send Tone Indications to an incoming
    	 * ISDN channel on a TE Port.
    	 * \note The "incoming_early_audio" boolean read in from /etc/asterisk/misdn.conf
    	 */
    
    	/*!
    	 * \brief TRUE if DTMF digits are to be passed inband only.
    	 * \note It is settable by the misdn_set_opt() application.
    	 */
    
    	 * \brief Pipe file descriptor handles array.
    	 * Read from pipe[0], write to pipe[1]
    
    
    	/*!
    	 * \brief Read buffer for inbound audio from pipe[0]
    	 */
    
    	char ast_rd_buf[4096];
    
    
    	/*!
    	 * \brief Inbound audio frame returned by misdn_read().
    	 */
    
    	/*!
    	 * \brief Fax detection option. (0:no 1:yes 2:yes+nojump)
    	 * \note The "faxdetect" option string read in from /etc/asterisk/misdn.conf
    	 * \note It is settable by the misdn_set_opt() application.
    	 */
    	int faxdetect;
    
    	/*!
    	 * \brief Number of seconds to detect a Fax machine when detection enabled.
    	 * \note 0 disables the timeout.
    	 * \note The "faxdetect_timeout" value read in from /etc/asterisk/misdn.conf
    	 */
    
    	int faxdetect_timeout;
    
    
    	/*!
    	 * \brief Starting time of fax detection with timeout when nonzero.
    	 */
    
    	struct timeval faxdetect_tv;
    
    
    	/*!
    	 * \brief TRUE if a fax has been detected.
    	 */
    
    	/*!
    	 * \brief TRUE if we will use the Asterisk DSP to detect DTMF/Fax
    	 * \note The "astdtmf" boolean read in from /etc/asterisk/misdn.conf
    	 */
    
    	/*!
    	 * \brief Jitterbuffer length
    	 * \note The "jitterbuffer" value read in from /etc/asterisk/misdn.conf
    	 */
    
    
    	/*!
    	 * \brief Jitterbuffer upper threshold
    	 * \note The "jitterbuffer_upper_threshold" value read in from /etc/asterisk/misdn.conf
    	 */
    
    	int jb_upper_threshold;
    
    
    	/*!
    	 * \brief Allocated jitterbuffer controller
    	 * \note misdn_jb_init() creates the jitterbuffer.
    
    	 * \note Must use misdn_jb_destroy() to clean up.
    
    	/*!
    	 * \brief Allocated DSP controller
    	 * \note ast_dsp_new() creates the DSP controller.
    
    	 * \note Must use ast_dsp_free() to clean up.
    
    
    	/*!
    	 * \brief Associated Asterisk channel structure.
    	 */
    
    	struct ast_channel * ast;
    
    	/*!
    	 * \brief Associated B channel structure.
    	 */
    
    	struct misdn_bchannel *bc;
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    	/*!
    	 * \brief Peer channel for which call completion was initialized.
    	 */
    	struct misdn_cc_caller *peer;
    
    	/*! \brief Associated call completion record ID (-1 if not associated) */
    	long record_id;
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    
    	 * \brief HELD channel call information
    
    	struct hold_info hold;
    
    	/*!
    	 * \brief From associated B channel: Layer 3 process ID
    
    	 * \note Used to find the HELD channel call record when retrieving a call.
    
    	 * \brief From associated B channel: B Channel mISDN driver layer ID from mISDN_get_layerid()
    	 * \note Used only for debug display messages.
    	 */
    
    	/*!
    	 * \brief Incoming call dialplan context identifier.
    	 * \note The "context" string read in from /etc/asterisk/misdn.conf
    	 */
    	char context[AST_MAX_CONTEXT];
    
    	/*!
    	 * \brief The configured music-on-hold class to use for this call.
    	 * \note The "musicclass" string read in from /etc/asterisk/misdn.conf
    	 */
    	char mohinterpret[MAX_MUSICCLASS];
    
    	/*!
    	 * \brief Number of outgoing audio frames dropped since last debug gripe message.
    	 */
    
    	/*!
    	 * \brief TRUE if we must do the ringback tones.
    	 * \note The "far_alerting" boolean read in from /etc/asterisk/misdn.conf
    	 */
    
    	/*!
    	 * \brief TRUE if NT should disconnect an overlap dialing call when a timeout occurs.
    	 * \note The "nttimeout" boolean read in from /etc/asterisk/misdn.conf
    	 */
    
    	/*!
    	 * \brief Tone zone sound used for dialtone generation.
    
    	 * \note Used as a boolean.  Non-NULL to prod generation if enabled.
    
    	struct ast_tone_zone_sound *ts;
    
    	/*!
    	 * \brief Enables overlap dialing for the set amount of seconds.  (0 = Disabled)
    	 * \note The "overlapdial" value read in from /etc/asterisk/misdn.conf
    	 */
    
    
    	/*!
    	 * \brief Overlap dialing timeout Task ID.  -1 if not running.
    	 */
    
    
    	/*!
    	 * \brief overlap_tv access lock.
    	 */
    
    
    	/*!
    	 * \brief Overlap timer start time.  Timer restarted for every digit received.
    	 */
    
    	/*!
    	 * \brief Next channel call record in the list.
    	 */
    
    
    void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
    void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
    
    static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame);
    
    	char *group;
    	int port;
    	int channel;
    	struct robin_list *next;
    	struct robin_list *prev;
    
    static void free_robin_list(void)
    
    	struct robin_list *r;
    	struct robin_list *next;
    
    	for (r = robin, robin = NULL; r; r = next) {
    		next = r->next;
    		ast_free(r->group);
    
    static struct robin_list *get_robin_position(char *group)
    
    	struct robin_list *iter = robin;
    	for (; iter; iter = iter->next) {
    
    		if (!strcasecmp(iter->group, group)) {
    
    	if (!new) {
    		return NULL;
    	}
    	new->group = ast_strdup(group);
    	if (!new->group) {
    		ast_free(new);
    		return NULL;
    	}
    
    	new->channel = 1;
    	if (robin) {
    		new->next = robin;
    		robin->prev = new;
    	}
    	robin = new;
    	return robin;
    }
    
    
    /*! \brief the main schedule context for stuff like l1 watcher, overlap dial, ... */
    
    static struct ast_sched_context *misdn_tasks = NULL;
    
    static int *misdn_ports;
    
    
    static void chan_misdn_log(int level, int port, char *tmpl, ...)
    
    	__attribute__((format(printf, 3, 4)));
    
    static struct ast_channel *misdn_new(struct chan_list *cl, int state,  char *exten, char *callerid, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int port, int c);
    
    static void send_digit_to_chan(struct chan_list *cl, char digit);
    
    #define MISDN_ASTERISK_TECH_PVT(ast) ast_channel_tech_pvt(ast)
    #define MISDN_ASTERISK_TECH_PVT_SET(ast, value) ast_channel_tech_pvt_set(ast, value)
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    static const char misdn_type[] = "mISDN";
    
    static int *misdn_debug;
    static int *misdn_debug_only;
    static int max_ports;
    
    static int *misdn_in_calls;
    static int *misdn_out_calls;
    
    
    /*!
     * \brief Global channel call record list head.
     */
    
    static struct chan_list *cl_te=NULL;
    static ast_mutex_t cl_te_lock;
    
    cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data);
    
    
    static int send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch);
    
    static void cl_queue_chan(struct chan_list *chan);
    
    static void hanguptone_indicate(struct chan_list *cl);
    
    
    static int start_bc_tones(struct chan_list *cl);
    static int stop_bc_tones(struct chan_list *cl);
    
    static void release_chan_early(struct chan_list *ch);
    static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc);
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    static const char misdn_command_name[] = "misdn_command";
    
    static int misdn_command_exec(struct ast_channel *chan, const char *data);
    
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    static int misdn_check_l2l1(struct ast_channel *chan, const char *data);
    static int misdn_set_opt_exec(struct ast_channel *chan, const char *data);
    static int misdn_facility_exec(struct ast_channel *chan, const char *data);
    
    int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len);
    
    
    void debug_numtype(int port, int numtype, char *type);
    
    int add_out_calls(int port);
    int add_in_calls(int port);
    
    
    
    #ifdef MISDN_1_2
    static int update_pipeline_config(struct misdn_bchannel *bc);
    #else
    
    static int update_ec_config(struct misdn_bchannel *bc);
    
    /*************** Helpers *****************/
    
    
    static int misdn_chan_is_valid(struct chan_list *ch)
    {
    	struct chan_list *list;
    
    	ast_mutex_lock(&cl_te_lock);
    	for (list = cl_te; list; list = list->next) {
    		if (list == ch) {
    			ast_mutex_unlock(&cl_te_lock);
    			return 1;
    		}
    	}
    	ast_mutex_unlock(&cl_te_lock);
    
    	return 0;
    }
    
    
    /*! Returns a reference to the found chan_list. */
    
    static struct chan_list *get_chan_by_ast(struct ast_channel *ast)
    
    	ast_mutex_lock(&cl_te_lock);
    
    	for (tmp = cl_te; tmp; tmp = tmp->next) {
    		if (tmp->ast == ast) {
    
    			chan_list_ref(tmp, "Found chan_list by ast");
    
    			ast_mutex_unlock(&cl_te_lock);
    
    	ast_mutex_unlock(&cl_te_lock);
    
    #endif	/* defined(mISDN_NATIVE_BRIDGING) */
    
    /*! Returns a reference to the found chan_list. */
    
    static struct chan_list *get_chan_by_ast_name(const char *name)
    
    	ast_mutex_lock(&cl_te_lock);
    
    	for (tmp = cl_te; tmp; tmp = tmp->next) {
    
    		if (tmp->ast && strcmp(ast_channel_name(tmp->ast), name) == 0) {
    
    			chan_list_ref(tmp, "Found chan_list by ast name");
    
    			ast_mutex_unlock(&cl_te_lock);
    
    	ast_mutex_unlock(&cl_te_lock);
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    
     * \brief Destroy the misdn_cc_ds_info datastore payload
    
     * \param[in] data the datastore payload, a reference to an misdn_cc_caller
    
     * \details
     * Since the payload is a reference to an astobj2 object, we just decrement its
     * reference count.  Before doing so, we NULL out the channel pointer inside of
     * the misdn_cc_caller instance.  This function will be called in one of two
     * cases.  In both cases, we no longer need the channel pointer:
     *
     *  - The original channel that initialized call completion services, the same
     *    channel that is stored here, has been destroyed early.  This could happen
     *    if it transferred the mISDN channel, for example.
     *
     *  - The mISDN channel that had this datastore inherited on to it is now being
     *    destroyed.  If this is the case, then the call completion events have
     *    already occurred and the appropriate channel variables have already been
     *    set on the original channel that requested call completion services.
     *
     * \return Nothing
    
    static void misdn_cc_ds_destroy(void *data)
    
    	struct misdn_cc_caller *cc_caller = data;
    
    	ao2_lock(cc_caller);
    	cc_caller->chan = NULL;
    	ao2_unlock(cc_caller);
    
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    
     * \brief Duplicate the misdn_cc_ds_info datastore payload
    
     * \param[in] data the datastore payload, a reference to an misdn_cc_caller
    
     * \details
     * All we need to do is bump the reference count and return the same instance.
     *
     * \return A reference to an instance of a misdn_cc_caller
    
    static void *misdn_cc_ds_duplicate(void *data)
    
    	struct misdn_cc_caller *cc_caller = data;
    
    	return cc_caller;
    }
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    static const struct ast_datastore_info misdn_cc_ds_info = {
    	.type      = "misdn_cc",
    	.destroy   = misdn_cc_ds_destroy,
    	.duplicate = misdn_cc_ds_duplicate,
    };
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    /*!
     * \internal
     * \brief Set a channel var on the peer channel for call completion services
     *
     * \param[in] peer The peer that initialized call completion services
     * \param[in] var The variable name to set
     * \param[in] value The variable value to set
     *
     * This function may be called from outside of the channel thread.  It handles
     * the fact that the peer channel may be hung up and destroyed at any time.
     *
     * \return nothing
     */
    static void misdn_cc_set_peer_var(struct misdn_cc_caller *peer, const char *var,
    	const char *value)
    {
    	ao2_lock(peer);
    
    	/*! \todo XXX This nastiness can go away once ast_channel is ref counted! */
    	while (peer->chan && ast_channel_trylock(peer->chan)) {
    		ao2_unlock(peer);
    		sched_yield();
    		ao2_lock(peer);
    	}
    
    	if (peer->chan) {
    		pbx_builtin_setvar_helper(peer->chan, var, value);
    		ast_channel_unlock(peer->chan);
    
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    
     * \brief Get a reference to the CC caller if it exists
    
    static struct misdn_cc_caller *misdn_cc_caller_get(struct ast_channel *chan)
    
    	struct ast_datastore *datastore;
    	struct misdn_cc_caller *cc_caller;
    
    	if (!(datastore = ast_channel_datastore_find(chan, &misdn_cc_ds_info, NULL))) {
    		ast_channel_unlock(chan);
    		return NULL;
    	}
    
    	ao2_ref(datastore->data, +1);
    	cc_caller = datastore->data;
    
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    
     * \brief Find the call completion record given the record id.
    
     * \retval pointer to found call completion record
     * \retval NULL if not found
     *
     * \note Assumes the misdn_cc_records_db lock is already obtained.
    
    static struct misdn_cc_record *misdn_cc_find_by_id(long record_id)
    
    	struct misdn_cc_record *current;
    
    	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
    		if (current->record_id == record_id) {
    			/* Found the record */
    			break;
    		}
    
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    
     * \brief Find the call completion record given the port and call linkage id.
    
     * \param port Logical port number
     * \param linkage_id Call linkage ID number from switch.
    
     * \retval pointer to found call completion record
     * \retval NULL if not found
     *
     * \note Assumes the misdn_cc_records_db lock is already obtained.
    
    static struct misdn_cc_record *misdn_cc_find_by_linkage(int port, int linkage_id)
    
    	struct misdn_cc_record *current;
    
    	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
    		if (current->port == port
    			&& !current->ptp
    			&& current->mode.ptmp.linkage_id == linkage_id) {
    			/* Found the record */
    			break;
    		}
    	}
    
    	return current;
    }
    #endif	/* defined(AST_MISDN_ENHANCEMENTS) */
    
    #if defined(AST_MISDN_ENHANCEMENTS)
    /*!
     * \internal
     * \brief Find the call completion record given the port and outstanding invocation id.
     *
     * \param port Logical port number
     * \param invoke_id Outstanding message invocation ID number.
     *
     * \retval pointer to found call completion record
     * \retval NULL if not found
     *
     * \note Assumes the misdn_cc_records_db lock is already obtained.
     */