aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrussell <russell@f38db490-d61c-443f-a65b-d21fe96a405b>2008-02-26 00:31:40 +0000
committerrussell <russell@f38db490-d61c-443f-a65b-d21fe96a405b>2008-02-26 00:31:40 +0000
commit0cc911d8cc93777118f6727c03f41df5a36e735d (patch)
tree5bb7d2ab605bcda1872e7baee221446ceae1e15d
parentb27ccb23172596d02efc48ad86102848bad7b715 (diff)
Merged revisions 104119 via svnmerge from
https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r104119 | russell | 2008-02-25 18:25:29 -0600 (Mon, 25 Feb 2008) | 33 lines Merge changes from team/russell/smdi-1.4 This commit brings in a significant set of changes to the SMDI support in Asterisk. There were a number of bugs in the current implementation, most notably being that it was very likely on busy systems to pop off the wrong message from the SMDI message queue. So, this set of changes fixes the issues discovered as well as introducing some new ways to use the SMDI support which are required to avoid the bugs with grabbing the wrong message off of the queue. This code introduces a new interface to SMDI, with two dialplan functions. First, you get an SMDI message in the dialplan using SMDI_MSG_RETRIEVE() and then you access details in the message using the SMDI_MSG() function. A side benefit of this is that it now supports more than just chan_zap. For example, with this implementation, you can have some FXO lines being terminated on a SIP gateway, but the SMDI link in Asterisk. Another issue with the current implementation is that it is quite common that the station ID that comes in on the SMDI link is not necessarily the same as the Asterisk voicemail box. There are now additional directives in the smdi.conf configuration file which let you map SMDI station IDs to Asterisk voicemail boxes. Yet another issue with the current SMDI support was related to MWI reporting over the SMDI link. The current code could only report a MWI change when the change was made by someone calling into voicemail. If the change was made by some other entity (such as with IMAP storage, or with a web interface of some kind), then the MWI change would never be sent. The SMDI module can now poll for MWI changes if configured to do so. This work was inspired by and primarily done for the University of Pennsylvania. (also related to issue #9260) ........ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@104120 f38db490-d61c-443f-a65b-d21fe96a405b
-rw-r--r--apps/app_voicemail.c8
-rw-r--r--channels/chan_zap.c2
-rw-r--r--configs/smdi.conf.sample32
-rw-r--r--include/asterisk/smdi.h122
-rw-r--r--res/res_smdi.c1134
5 files changed, 975 insertions, 323 deletions
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 9bcb8f159..ec901ac03 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -2976,8 +2976,8 @@ static void run_externnotify(char *context, char *extension)
else
ast_smdi_mwi_unset(smdi_iface, extension);
- if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
- ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
+ if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) {
+ ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension);
if (!strncmp(mwi_msg->cause, "INV", 3))
ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
else if (!strncmp(mwi_msg->cause, "BLK", 3))
@@ -2985,7 +2985,7 @@ static void run_externnotify(char *context, char *extension)
ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
} else {
- ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
+ ast_debug(1, "Successfully executed SMDI MWI change for %s\n", extension);
}
}
@@ -8411,8 +8411,6 @@ static int load_config(int reload)
}
if (!smdi_iface) {
ast_log(LOG_ERROR, "No valid SMDI interface specfied, disabling SMDI voicemail notification\n");
- } else {
- ast_debug(1, "Using SMDI port %s\n", smdi_iface->name);
}
}
diff --git a/channels/chan_zap.c b/channels/chan_zap.c
index 36e0f934e..f09dd621f 100644
--- a/channels/chan_zap.c
+++ b/channels/chan_zap.c
@@ -2764,7 +2764,7 @@ static void destroy_zt_pvt(struct zt_pvt **pvt)
if (p->next)
p->next->prev = p->prev;
if (p->use_smdi)
- ASTOBJ_UNREF(p->smdi_iface, ast_smdi_interface_destroy);
+ ast_smdi_interface_unref(p->smdi_iface);
if (p->mwi_event_sub)
ast_event_unsubscribe(p->mwi_event_sub);
if (p->vars)
diff --git a/configs/smdi.conf.sample b/configs/smdi.conf.sample
index 0325eba48..669530e81 100644
--- a/configs/smdi.conf.sample
+++ b/configs/smdi.conf.sample
@@ -41,3 +41,35 @@
;msgexpirytime = 30000
;smdiport => /dev/ttyS0
+
+
+[mailboxes]
+; This section configures parameters related to MWI handling for the SMDI link.
+
+; This option configures the polling interval used to check to see if the
+; mailboxes have any new messages. This option is specified in seconds.
+; The default value is 10 seconds.
+;
+;pollinginterval=10
+
+; Every other entry in this section of the configuration file is interpreted as
+; a mapping between the mailbox ID on the SMDI link, and the local Asterisk
+; mailbox name. In many cases, they are the same thing, but they still must be
+; listed here so that this module knows which mailboxes it needs to pay
+; attention to.
+;
+; Syntax:
+; <SMDI mailbox ID>=<Asterisk Mailbox Name>[@Asterisk Voicemail Context]
+;
+; If no Asterisk voicemail context is specified, "default" will be assumed.
+;
+; Before specifying mailboxes, you must specify an SMDI interface. All mailbox
+; definitions that follow will correspond to that SMDI interface. If you specify
+; another interface, then all definitions following that will correspond to the
+; new interface.
+;
+;smdiport=/dev/ttyS0
+;2565551234=1234@vmcontext1
+;2565555678=5678@vmcontext2
+;smdiport=/dev/ttyS1
+;2565559999=9999
diff --git a/include/asterisk/smdi.h b/include/asterisk/smdi.h
index 6260d9d2d..8f098abdd 100644
--- a/include/asterisk/smdi.h
+++ b/include/asterisk/smdi.h
@@ -1,9 +1,10 @@
/*
* Asterisk -- A telephony toolkit for Linux.
*
- * Copyright (C) 2005-2006, Digium, Inc.
+ * Copyright (C) 2005-2008, Digium, Inc.
*
* Matthew A. Nicholson <mnicholson@digium.com>
+ * Russell Bryant <russell@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@@ -20,6 +21,7 @@
* \file
* \brief SMDI support for Asterisk.
* \author Matthew A. Nicholson <mnicholson@digium.com>
+ * \author Russell Bryant <russell@digium.com>
*/
@@ -73,16 +75,6 @@ struct ast_smdi_md_message {
struct timeval timestamp; /* a timestamp for the message */
};
-/*! \brief SMDI message desk message queue. */
-struct ast_smdi_md_queue {
- ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
-};
-
-/*! \brief SMDI message waiting indicator message queue. */
-struct ast_smdi_mwi_queue {
- ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
-};
-
/*!
* \brief SMDI interface structure.
*
@@ -90,38 +82,114 @@ struct ast_smdi_mwi_queue {
* should be monitored for SMDI activity. The structure contains a message
* queue of messages that have been received on the interface.
*/
-struct ast_smdi_interface {
- ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
- struct ast_smdi_md_queue md_q;
- struct ast_smdi_mwi_queue mwi_q;
- FILE *file;
- int fd;
- pthread_t thread;
- struct termios mode;
- int msdstrip;
- long msg_expiry;
-};
+struct ast_smdi_interface;
+void ast_smdi_interface_unref(struct ast_smdi_interface *iface);
-/* MD message queue functions */
+/*!
+ * \brief Get the next SMDI message from the queue.
+ * \param iface a pointer to the interface to use.
+ *
+ * This function pulls the first unexpired message from the SMDI message queue
+ * on the specified interface. It will purge all expired SMDI messages before
+ * returning.
+ *
+ * \return the next SMDI message, or NULL if there were no pending messages.
+ */
struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface);
+
+/*!
+ * \brief Get the next SMDI message from the queue.
+ * \param iface a pointer to the interface to use.
+ * \param timeout the time to wait before returning in milliseconds.
+ *
+ * This function pulls a message from the SMDI message queue on the specified
+ * interface. If no message is available this function will wait the specified
+ * amount of time before returning.
+ *
+ * \return the next SMDI message, or NULL if there were no pending messages and
+ * the timeout has expired.
+ */
struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout);
+
+/*!
+ * \brief Put an SMDI message back in the front of the queue.
+ * \param iface a pointer to the interface to use.
+ * \param md_msg a pointer to the message to use.
+ *
+ * This function puts a message back in the front of the specified queue. It
+ * should be used if a message was popped but is not going to be processed for
+ * some reason, and the message needs to be returned to the queue.
+ */
void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
-/* MWI message queue functions */
+/*!
+ * \brief Get the next SMDI message from the queue.
+ * \param iface a pointer to the interface to use.
+ *
+ * This function pulls the first unexpired message from the SMDI message queue
+ * on the specified interface. It will purge all expired SMDI messages before
+ * returning.
+ *
+ * \return the next SMDI message, or NULL if there were no pending messages.
+ */
struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface);
+
+/*!
+ * \brief Get the next SMDI message from the queue.
+ * \param iface a pointer to the interface to use.
+ * \param timeout the time to wait before returning in milliseconds.
+ *
+ * This function pulls a message from the SMDI message queue on the specified
+ * interface. If no message is available this function will wait the specified
+ * amount of time before returning.
+ *
+ * \return the next SMDI message, or NULL if there were no pending messages and
+ * the timeout has expired.
+ */
struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout);
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface,
+ int timeout, const char *station);
+
+/*!
+ * \brief Put an SMDI message back in the front of the queue.
+ * \param iface a pointer to the interface to use.
+ * \param mwi_msg a pointer to the message to use.
+ *
+ * This function puts a message back in the front of the specified queue. It
+ * should be used if a message was popped but is not going to be processed for
+ * some reason, and the message needs to be returned to the queue.
+ */
void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
+/*!
+ * \brief Find an SMDI interface with the specified name.
+ * \param iface_name the name/port of the interface to search for.
+ *
+ * \return a pointer to the interface located or NULL if none was found. This
+ * actually returns an ASTOBJ reference and should be released using
+ * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
+ */
struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name);
-/* MWI functions */
+/*!
+ * \brief Set the MWI indicator for a mailbox.
+ * \param iface the interface to use.
+ * \param mailbox the mailbox to use.
+ */
int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox);
+
+/*!
+ * \brief Unset the MWI indicator for a mailbox.
+ * \param iface the interface to use.
+ * \param mailbox the mailbox to use.
+ */
int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox);
+/*! \brief ast_smdi_md_message destructor. */
void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg);
-void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg);
-void ast_smdi_interface_destroy(struct ast_smdi_interface *iface);
+/*! \brief ast_smdi_mwi_message destructor. */
+void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg);
#endif /* !ASTERISK_SMDI_H */
diff --git a/res/res_smdi.c b/res/res_smdi.c
index 1df720e83..ec0b2c4f9 100644
--- a/res/res_smdi.c
+++ b/res/res_smdi.c
@@ -1,9 +1,10 @@
/*
* Asterisk -- A telephony toolkit for Linux.
*
- * Copyright (C) 2005-2006, Digium, Inc.
+ * Copyright (C) 2005-2008, Digium, Inc.
*
* Matthew A. Nicholson <mnicholson@digium.com>
+ * Russell Bryant <russell@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@@ -20,6 +21,10 @@
* \file
* \brief SMDI support for Asterisk.
* \author Matthew A. Nicholson <mnicholson@digium.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * Here is a useful mailing list post that describes SMDI protocol details:
+ * \ref http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html
*/
#include "asterisk.h"
@@ -38,25 +43,120 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/config.h"
#include "asterisk/astobj.h"
#include "asterisk/io.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/channel.h"
/* Message expiry time in milliseconds */
#define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
static const char config_file[] = "smdi.conf";
-static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
-static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
-
-static void *smdi_read(void *iface_p);
-static int smdi_load(int reload);
-
-struct module_symbols *me; /* initialized in load_module() */
+/*! \brief SMDI message desk message queue. */
+struct ast_smdi_md_queue {
+ ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
+};
+
+/*! \brief SMDI message waiting indicator message queue. */
+struct ast_smdi_mwi_queue {
+ ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
+};
+
+struct ast_smdi_interface {
+ ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
+ struct ast_smdi_md_queue md_q;
+ ast_mutex_t md_q_lock;
+ ast_cond_t md_q_cond;
+ struct ast_smdi_mwi_queue mwi_q;
+ ast_mutex_t mwi_q_lock;
+ ast_cond_t mwi_q_cond;
+ FILE *file;
+ int fd;
+ pthread_t thread;
+ struct termios mode;
+ int msdstrip;
+ long msg_expiry;
+};
/*! \brief SMDI interface container. */
struct ast_smdi_interface_container {
ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
} smdi_ifaces;
+/*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
+struct mailbox_mapping {
+ /*! This is the current state of the mailbox. It is simply on or
+ * off to indicate if there are messages waiting or not. */
+ unsigned int cur_state:1;
+ /*! A Pointer to the appropriate SMDI interface */
+ struct ast_smdi_interface *iface;
+ AST_DECLARE_STRING_FIELDS(
+ /*! The Name of the mailbox for the SMDI link. */
+ AST_STRING_FIELD(smdi);
+ /*! The name of the mailbox on the Asterisk side */
+ AST_STRING_FIELD(mailbox);
+ /*! The name of the voicemail context in use */
+ AST_STRING_FIELD(context);
+ );
+ AST_LIST_ENTRY(mailbox_mapping) entry;
+};
+
+/*! 10 seconds */
+#define DEFAULT_POLLING_INTERVAL 10
+
+/*! \brief Data that gets used by the SMDI MWI monitoring thread */
+static struct {
+ /*! The thread ID */
+ pthread_t thread;
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ /*! A list of mailboxes that need to be monitored */
+ AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
+ /*! Polling Interval for checking mailbox status */
+ unsigned int polling_interval;
+ /*! Set to 1 to tell the polling thread to stop */
+ unsigned int stop:1;
+ /*! The time that the last poll began */
+ struct timeval last_poll;
+} mwi_monitor = {
+ .thread = AST_PTHREADT_NULL,
+};
+
+static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
+{
+ if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
+ pthread_cancel(iface->thread);
+ pthread_join(iface->thread, NULL);
+ }
+
+ iface->thread = AST_PTHREADT_STOP;
+
+ if (iface->file)
+ fclose(iface->file);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
+ ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
+ ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
+
+ ast_mutex_destroy(&iface->md_q_lock);
+ ast_cond_destroy(&iface->md_q_cond);
+
+ ast_mutex_destroy(&iface->mwi_q_lock);
+ ast_cond_destroy(&iface->mwi_q_cond);
+
+ free(iface);
+
+ ast_module_unref(ast_module_info->self);
+}
+
+void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
+{
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+}
+
/*!
* \internal
* \brief Push an SMDI message to the back of an interface's message queue.
@@ -65,7 +165,10 @@ struct ast_smdi_interface_container {
*/
static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
{
+ ast_mutex_lock(&iface->md_q_lock);
ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
+ ast_cond_broadcast(&iface->md_q_cond);
+ ast_mutex_unlock(&iface->md_q_lock);
}
/*!
@@ -76,15 +179,13 @@ static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct as
*/
static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
{
+ ast_mutex_lock(&iface->mwi_q_lock);
ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
+ ast_cond_broadcast(&iface->mwi_q_cond);
+ ast_mutex_unlock(&iface->mwi_q_lock);
}
-/*!
- * \brief Set the MWI indicator for a mailbox.
- * \param iface the interface to use.
- * \param mailbox the mailbox to use.
- */
-int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
+static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
{
FILE *file;
int i;
@@ -96,224 +197,263 @@ int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
ASTOBJ_WRLOCK(iface);
- fprintf(file, "OP:MWI ");
+ fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
for (i = 0; i < iface->msdstrip; i++)
fprintf(file, "0");
fprintf(file, "%s!\x04", mailbox);
+
fclose(file);
ASTOBJ_UNLOCK(iface);
ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
+
return 0;
}
-/*!
- * \brief Unset the MWI indicator for a mailbox.
- * \param iface the interface to use.
- * \param mailbox the mailbox to use.
- */
-int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
+int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
{
- FILE *file;
- int i;
-
- if (!(file = fopen(iface->name, "w"))) {
- ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
- return 1;
- }
-
- ASTOBJ_WRLOCK(iface);
-
- fprintf(file, "RMV:MWI ");
-
- for (i = 0; i < iface->msdstrip; i++)
- fprintf(file, "0");
-
- fprintf(file, "%s!\x04", mailbox);
- fclose(file);
+ return smdi_toggle_mwi(iface, mailbox, 1);
+}
- ASTOBJ_UNLOCK(iface);
- ast_debug(1, "Sent MWI unset message for %s on %s\n", mailbox, iface->name);
- return 0;
+int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
+{
+ return smdi_toggle_mwi(iface, mailbox, 0);
}
-/*!
- * \brief Put an SMDI message back in the front of the queue.
- * \param iface a pointer to the interface to use.
- * \param md_msg a pointer to the message to use.
- *
- * This function puts a message back in the front of the specified queue. It
- * should be used if a message was popped but is not going to be processed for
- * some reason, and the message needs to be returned to the queue.
- */
void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
{
+ ast_mutex_lock(&iface->md_q_lock);
ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
+ ast_cond_broadcast(&iface->md_q_cond);
+ ast_mutex_unlock(&iface->md_q_lock);
}
-/*!
- * \brief Put an SMDI message back in the front of the queue.
- * \param iface a pointer to the interface to use.
- * \param mwi_msg a pointer to the message to use.
- *
- * This function puts a message back in the front of the specified queue. It
- * should be used if a message was popped but is not going to be processed for
- * some reason, and the message needs to be returned to the queue.
- */
void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
{
+ ast_mutex_lock(&iface->mwi_q_lock);
ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
+ ast_cond_broadcast(&iface->mwi_q_cond);
+ ast_mutex_unlock(&iface->mwi_q_lock);
}
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- *
- * This function pulls the first unexpired message from the SMDI message queue
- * on the specified interface. It will purge all expired SMDI messages before
- * returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages.
- */
-struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
-{
- struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
- struct timeval now;
- long elapsed = 0;
+enum smdi_message_type {
+ SMDI_MWI,
+ SMDI_MD,
+};
- /* purge old messages */
- now = ast_tvnow();
- while (md_msg) {
- elapsed = ast_tvdiff_ms(now, md_msg->timestamp);
+static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ switch (type) {
+ case SMDI_MWI:
+ return ast_mutex_lock(&iface->mwi_q_lock);
+ case SMDI_MD:
+ return ast_mutex_lock(&iface->md_q_lock);
+ }
+
+ return -1;
+}
- if (elapsed > iface->msg_expiry) {
- /* found an expired message */
- ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
- ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue. Message was %ld milliseconds too old.\n",
- iface->name, elapsed - iface->msg_expiry);
- md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
- }
- else {
- /* good message, return it */
- break;
- }
+static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ switch (type) {
+ case SMDI_MWI:
+ return ast_mutex_unlock(&iface->mwi_q_lock);
+ case SMDI_MD:
+ return ast_mutex_unlock(&iface->md_q_lock);
}
- return md_msg;
+ return -1;
}
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- * \param timeout the time to wait before returning in milliseconds.
- *
- * This function pulls a message from the SMDI message queue on the specified
- * interface. If no message is available this function will wait the specified
- * amount of time before returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages and
- * the timeout has expired.
- */
-extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
+static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
{
- struct timeval start = ast_tvnow();
- long diff = 0;
- struct ast_smdi_md_message *msg;
+ switch (type) {
+ case SMDI_MWI:
+ return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
+ case SMDI_MD:
+ return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
+ }
+ return NULL;
+}
- while (diff < timeout) {
+static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
+{
+ struct ast_smdi_md_message *md_msg = msg;
+ struct ast_smdi_mwi_message *mwi_msg = msg;
+
+ switch (type) {
+ case SMDI_MWI:
+ return mwi_msg->timestamp;
+ case SMDI_MD:
+ return md_msg->timestamp;
+ }
- if ((msg = ast_smdi_md_message_pop(iface)))
- return msg;
+ return ast_tv(0, 0);
+}
- /* check timeout */
- diff = ast_tvdiff_ms(ast_tvnow(), start);
+static inline void unref_msg(void *msg, enum smdi_message_type type)
+{
+ struct ast_smdi_md_message *md_msg = msg;
+ struct ast_smdi_mwi_message *mwi_msg = msg;
+
+ switch (type) {
+ case SMDI_MWI:
+ ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+ case SMDI_MD:
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
}
-
- return (ast_smdi_md_message_pop(iface));
}
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- *
- * This function pulls the first unexpired message from the SMDI message queue
- * on the specified interface. It will purge all expired SMDI messages before
- * returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages.
- */
-extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
+static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
{
- struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
struct timeval now = ast_tvnow();
long elapsed = 0;
+ void *msg;
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
/* purge old messages */
- while (mwi_msg) {
- elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp);
+ while (msg) {
+ elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
if (elapsed > iface->msg_expiry) {
/* found an expired message */
- ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
- ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue. Message was %ld milliseconds too old.\n",
- iface->name, elapsed - iface->msg_expiry);
- mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
- }
- else {
- /* good message, return it */
+ unref_msg(msg, type);
+ ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
+ "Message was %ld milliseconds too old.\n",
+ iface->name, (type == SMDI_MD) ? "MD" : "MWI",
+ elapsed - iface->msg_expiry);
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
+ } else {
+ /* good message, put it back and return */
+ switch (type) {
+ case SMDI_MD:
+ ast_smdi_md_message_push(iface, msg);
+ break;
+ case SMDI_MWI:
+ ast_smdi_mwi_message_push(iface, msg);
+ break;
+ }
+ unref_msg(msg, type);
break;
}
}
+}
+
+static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ void *msg;
+
+ purge_old_messages(iface, type);
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
- return mwi_msg;
+ return msg;
}
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- * \param timeout the time to wait before returning in milliseconds.
- *
- * This function pulls a message from the SMDI message queue on the specified
- * interface. If no message is available this function will wait the specified
- * amount of time before returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages and
- * the timeout has expired.
- */
-extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
+static void *smdi_msg_find(struct ast_smdi_interface *iface,
+ enum smdi_message_type type, const char *station)
{
- struct timeval start = ast_tvnow();
+ void *msg = NULL;
+
+ purge_old_messages(iface, type);
+
+ switch (type) {
+ case SMDI_MD:
+ msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, station);
+ break;
+ case SMDI_MWI:
+ msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, station);
+ break;
+ }
+
+ return msg;
+}
+
+static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
+ enum smdi_message_type type, const char *station)
+{
+ struct timeval start;
long diff = 0;
- struct ast_smdi_mwi_message *msg;
+ void *msg;
while (diff < timeout) {
+ struct timespec ts = { 0, };
+ struct timeval tv;
+
+ lock_msg_q(iface, type);
- if ((msg = ast_smdi_mwi_message_pop(iface)))
+ if ((msg = smdi_msg_find(iface, type, station))) {
+ unlock_msg_q(iface, type);
return msg;
+ }
+
+ tv = ast_tvadd(start, ast_tv(0, timeout));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+
+ /* If there were no messages in the queue, then go to sleep until one
+ * arrives. */
+
+ ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
+
+ if ((msg = smdi_msg_find(iface, type, station))) {
+ unlock_msg_q(iface, type);
+ return msg;
+ }
+
+ unlock_msg_q(iface, type);
/* check timeout */
diff = ast_tvdiff_ms(ast_tvnow(), start);
}
- return (ast_smdi_mwi_message_pop(iface));
+ return NULL;
+}
+
+struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
+{
+ return smdi_msg_pop(iface, SMDI_MD);
}
-/*!
- * \brief Find an SMDI interface with the specified name.
- * \param iface_name the name/port of the interface to search for.
- *
- * \return a pointer to the interface located or NULL if none was found. This
- * actually returns an ASTOBJ reference and should be released using
- * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
- */
-extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
+struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
+{
+ return smdi_message_wait(iface, timeout, SMDI_MD, NULL);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
+{
+ return smdi_msg_pop(iface, SMDI_MWI);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
+{
+ return smdi_message_wait(iface, timeout, SMDI_MWI, NULL);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
+ const char *station)
+{
+ return smdi_message_wait(iface, timeout, SMDI_MWI, station);
+}
+
+struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
{
return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
}
-/*! \brief Read an SMDI message.
+/*!
+ * \internal
+ * \brief Read an SMDI message.
*
* \param iface_p the SMDI interface to read from.
*
@@ -335,122 +475,166 @@ static void *smdi_read(void *iface_p)
/* check if this is the start of a message */
if (!start) {
- if (c == 'M')
+ if (c == 'M') {
+ ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
start = 1;
+ }
+ continue;
}
- else { /* Determine if this is a MD or MWI message */
- if(c == 'D') { /* MD message */
- start = 0;
+
+ if (c == 'D') { /* MD message */
+ start = 0;
+
+ ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
+
+ if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ return NULL;
+ }
+
+ ASTOBJ_INIT(md_msg);
+
+ /* read the message desk number */
+ for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
+ md_msg->mesg_desk_num[i] = fgetc(iface->file);
+ ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
+ }
+
+ md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
+
+ ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
- if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
- ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
- return NULL;
+ /* read the message desk terminal number */
+ for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
+ md_msg->mesg_desk_term[i] = fgetc(iface->file);
+ ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
+ }
+
+ md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
+
+ ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
+
+ /* read the message type */
+ md_msg->type = fgetc(iface->file);
+
+ ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
+
+ /* read the forwarding station number (may be blank) */
+ cp = &md_msg->fwd_st[0];
+ for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
+ if ((c = fgetc(iface->file)) == ' ') {
+ *cp = '\0';
+ ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
+ break;
}
-
- ASTOBJ_INIT(md_msg);
-
- /* read the message desk number */
- for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++)
- md_msg->mesg_desk_num[i] = fgetc(iface->file);
-
- md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0';
-
- /* read the message desk terminal number */
- for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++)
- md_msg->mesg_desk_term[i] = fgetc(iface->file);
-
- md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0';
-
- /* read the message type */
- md_msg->type = fgetc(iface->file);
-
- /* read the forwarding station number (may be blank) */
- cp = &md_msg->fwd_st[0];
- for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
- if((c = fgetc(iface->file)) == ' ') {
- *cp = '\0';
- break;
- }
- /* store c in md_msg->fwd_st */
- if( i >= iface->msdstrip)
- *cp++ = c;
+ /* store c in md_msg->fwd_st */
+ if (i >= iface->msdstrip) {
+ ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
+ *cp++ = c;
+ } else {
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the fwd station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
}
+ }
- /* make sure the value is null terminated, even if this truncates it */
- md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
- cp = NULL;
-
- /* read the calling station number (may be blank) */
- cp = &md_msg->calling_st[0];
- for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
- if (!isdigit((c = fgetc(iface->file)))) {
- *cp = '\0';
- break;
+ /* make sure the value is null terminated, even if this truncates it */
+ md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
+ cp = NULL;
+
+ ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
+
+ /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
+ * up a message on this field */
+ ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
+
+ /* read the calling station number (may be blank) */
+ cp = &md_msg->calling_st[0];
+ for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
+ if (!isdigit((c = fgetc(iface->file)))) {
+ *cp = '\0';
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer because it's not a digit\n", c);
+ if (c == ' ') {
+ /* Don't break on a space. We may read the space before the calling station
+ * here if the forwarding station buffer filled up. */
+ i--; /* We're still on the same character */
+ continue;
}
+ break;
+ }
- /* store c in md_msg->calling_st */
- if (i >= iface->msdstrip)
- *cp++ = c;
+ /* store c in md_msg->calling_st */
+ if (i >= iface->msdstrip) {
+ ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
+ *cp++ = c;
+ } else {
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
}
+ }
+
+ /* make sure the value is null terminated, even if this truncates it */
+ md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
+ cp = NULL;
+
+ ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
- /* make sure the value is null terminated, even if this truncates it */
- md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
- cp = NULL;
+ /* add the message to the message queue */
+ md_msg->timestamp = ast_tvnow();
+ ast_smdi_md_message_push(iface, md_msg);
+ ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
+
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
- /* add the message to the message queue */
- md_msg->timestamp = ast_tvnow();
- ast_smdi_md_message_push(iface, md_msg);
- ast_debug(1, "Recieved SMDI MD message on %s\n", iface->name);
-
- ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+ } else if (c == 'W') { /* MWI message */
+ start = 0;
- } else if(c == 'W') { /* MWI message */
- start = 0;
+ ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
- if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
- ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
- return NULL;
- }
+ if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
+ ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+ return NULL;
+ }
- ASTOBJ_INIT(mwi_msg);
-
- /* discard the 'I' (from 'MWI') */
- fgetc(iface->file);
-
- /* read the forwarding station number (may be blank) */
- cp = &mwi_msg->fwd_st[0];
- for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
- if ((c = fgetc(iface->file)) == ' ') {
- *cp = '\0';
- break;
- }
+ ASTOBJ_INIT(mwi_msg);
- /* store c in md_msg->fwd_st */
- if (i >= iface->msdstrip)
- *cp++ = c;
+ /* discard the 'I' (from 'MWI') */
+ fgetc(iface->file);
+
+ /* read the forwarding station number (may be blank) */
+ cp = &mwi_msg->fwd_st[0];
+ for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
+ if ((c = fgetc(iface->file)) == ' ') {
+ *cp = '\0';
+ break;
}
- /* make sure the station number is null terminated, even if this will truncate it */
- mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
- cp = NULL;
-
- /* read the mwi failure cause */
- for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++)
- mwi_msg->cause[i] = fgetc(iface->file);
-
- mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0';
-
- /* add the message to the message queue */
- mwi_msg->timestamp = ast_tvnow();
- ast_smdi_mwi_message_push(iface, mwi_msg);
- ast_debug(1, "Recieved SMDI MWI message on %s\n", iface->name);
-
- ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
- } else {
- ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
- start = 0;
+ /* store c in md_msg->fwd_st */
+ if (i >= iface->msdstrip)
+ *cp++ = c;
}
+
+ /* make sure the station number is null terminated, even if this will truncate it */
+ mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
+ cp = NULL;
+
+ /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
+ * up a message on this field */
+ ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
+
+ /* read the mwi failure cause */
+ for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
+ mwi_msg->cause[i] = fgetc(iface->file);
+
+ mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
+
+ /* add the message to the message queue */
+ mwi_msg->timestamp = ast_tvnow();
+ ast_smdi_mwi_message_push(iface, mwi_msg);
+ ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
+
+ ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+ } else {
+ ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
+ start = 0;
}
}
@@ -459,38 +643,130 @@ static void *smdi_read(void *iface_p)
return NULL;
}
-/*! \brief ast_smdi_md_message destructor. */
void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
{
ast_free(msg);
}
-/*! \brief ast_smdi_mwi_message destructor. */
void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
{
ast_free(msg);
}
-/*! \brief ast_smdi_interface destructor. */
-void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
+static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
{
- if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
- pthread_cancel(iface->thread);
- pthread_join(iface->thread, NULL);
- }
-
- iface->thread = AST_PTHREADT_STOP;
-
- if(iface->file)
- fclose(iface->file);
+ ast_string_field_free_memory(mm);
+ ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
+ free(mm);
+}
+
+static void destroy_all_mailbox_mappings(void)
+{
+ struct mailbox_mapping *mm;
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
+ destroy_mailbox_mapping(mm);
+ ast_mutex_unlock(&mwi_monitor.lock);
+}
+
+static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
+{
+ struct mailbox_mapping *mm;
+ char *mailbox, *context;
+
+ if (!(mm = ast_calloc(1, sizeof(*mm))))
+ return;
- ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
- ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
- ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
- ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
- ast_free(iface);
+ if (ast_string_field_init(mm, 32)) {
+ free(mm);
+ return;
+ }
- ast_module_unref(ast_module_info->self);
+ ast_string_field_set(mm, smdi, var->name);
+
+ context = ast_strdupa(var->value);
+ mailbox = strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ ast_string_field_set(mm, mailbox, mailbox);
+ ast_string_field_set(mm, context, context);
+
+ mm->iface = ASTOBJ_REF(iface);
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
+ ast_mutex_unlock(&mwi_monitor.lock);
+}
+
+/*!
+ * \note Called with the mwi_monitor.lock locked
+ */
+static void poll_mailbox(struct mailbox_mapping *mm)
+{
+ char buf[1024];
+ unsigned int state;
+
+ snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
+
+ state = !!ast_app_has_voicemail(mm->mailbox, NULL);
+
+ if (state != mm->cur_state) {
+ if (state)
+ ast_smdi_mwi_set(mm->iface, mm->smdi);
+ else
+ ast_smdi_mwi_unset(mm->iface, mm->smdi);
+
+ mm->cur_state = state;
+ }
+}
+
+static void *mwi_monitor_handler(void *data)
+{
+ while (!mwi_monitor.stop) {
+ struct timespec ts = { 0, };
+ struct timeval tv;
+ struct mailbox_mapping *mm;
+
+ ast_mutex_lock(&mwi_monitor.lock);
+
+ mwi_monitor.last_poll = ast_tvnow();
+
+ AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
+ poll_mailbox(mm);
+
+ /* Sleep up to the configured polling interval. Allow unload_module()
+ * to signal us to wake up and exit. */
+ tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
+
+ ast_mutex_unlock(&mwi_monitor.lock);
+ }
+
+ return NULL;
+}
+
+static struct ast_smdi_interface *alloc_smdi_interface(void)
+{
+ struct ast_smdi_interface *iface;
+
+ if (!(iface = ast_calloc(1, sizeof(*iface))))
+ return NULL;
+
+ ASTOBJ_INIT(iface);
+ ASTOBJ_CONTAINER_INIT(&iface->md_q);
+ ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
+
+ ast_mutex_init(&iface->md_q_lock);
+ ast_cond_init(&iface->md_q_cond, NULL);
+
+ ast_mutex_init(&iface->mwi_q_lock);
+ ast_cond_init(&iface->mwi_q_cond, NULL);
+
+ return iface;
}
/*!
@@ -541,11 +817,11 @@ static int smdi_load(int reload)
if (!strcasecmp(v->name, "baudrate")) {
if (!strcasecmp(v->value, "9600"))
baud_rate = B9600;
- else if(!strcasecmp(v->value, "4800"))
+ else if (!strcasecmp(v->value, "4800"))
baud_rate = B4800;
- else if(!strcasecmp(v->value, "2400"))
+ else if (!strcasecmp(v->value, "2400"))
baud_rate = B2400;
- else if(!strcasecmp(v->value, "1200"))
+ else if (!strcasecmp(v->value, "1200"))
baud_rate = B1200;
else {
ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
@@ -605,14 +881,10 @@ static int smdi_load(int reload)
continue;
}
}
-
- if (!(iface = ast_calloc(1, sizeof(*iface))))
+
+ if (!(iface = alloc_smdi_interface()))
continue;
- ASTOBJ_INIT(iface);
- ASTOBJ_CONTAINER_INIT(&iface->md_q);
- ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
-
ast_copy_string(iface->name, v->value, sizeof(iface->name));
iface->thread = AST_PTHREADT_NULL;
@@ -666,7 +938,7 @@ static int smdi_load(int reload)
/* set the message expiry time */
iface->msg_expiry = msg_expiry;
- /* start the listner thread */
+ /* start the listener thread */
ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
@@ -681,8 +953,46 @@ static int smdi_load(int reload)
ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
}
}
+
+ destroy_all_mailbox_mappings();
+ mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+
+ iface = NULL;
+
+ for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
+ if (!strcasecmp(v->name, "smdiport")) {
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
+ if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
+ ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
+ continue;
+ }
+ } else if (!strcasecmp(v->name, "pollinginterval")) {
+ if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
+ ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
+ mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+ }
+ } else {
+ if (!iface) {
+ ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
+ continue;
+ }
+ append_mailbox_mapping(v, iface);
+ }
+ }
+
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
ast_config_destroy(conf);
+ if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
+ && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
+ ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
/* Prune any interfaces we should no longer monitor. */
if (reload)
ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
@@ -696,6 +1006,231 @@ static int smdi_load(int reload)
return res;
}
+struct smdi_msg_datastore {
+ unsigned int id;
+ struct ast_smdi_interface *iface;
+ struct ast_smdi_md_message *md_msg;
+};
+
+static void smdi_msg_datastore_destroy(void *data)
+{
+ struct smdi_msg_datastore *smd = data;
+
+ if (smd->iface)
+ ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
+
+ if (smd->md_msg)
+ ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
+
+ free(smd);
+}
+
+static const struct ast_datastore_info smdi_msg_datastore_info = {
+ .type = "SMDIMSG",
+ .destroy = smdi_msg_datastore_destroy,
+};
+
+static int smdi_msg_id;
+
+/*! In milliseconds */
+#define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
+
+static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(port);
+ AST_APP_ARG(station);
+ AST_APP_ARG(timeout);
+ );
+ unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+ int res = -1;
+ char *parse = NULL;
+ struct smdi_msg_datastore *smd = NULL;
+ struct ast_datastore *datastore = NULL;
+ struct ast_smdi_interface *iface = NULL;
+ struct ast_smdi_md_message *md_msg = NULL;
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
+ goto return_error;
+ }
+
+ if (!chan) {
+ ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
+ goto return_error;
+ }
+
+ ast_autoservice_start(chan);
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.port) || ast_strlen_zero(args.station)) {
+ ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
+ goto return_error;
+ }
+
+ if (!(iface = ast_smdi_interface_find(args.port))) {
+ ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
+ goto return_error;
+ }
+
+ if (!ast_strlen_zero(args.timeout)) {
+ if (sscanf(args.timeout, "%u", &timeout) != 1) {
+ ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
+ timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+ }
+ }
+
+ if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.station))) {
+ ast_log(LOG_WARNING, "No SMDI message retrieved for station '%s' after "
+ "waiting %u ms.\n", args.station, timeout);
+ goto return_error;
+ }
+
+ if (!(smd = ast_calloc(1, sizeof(*smd))))
+ goto return_error;
+
+ smd->iface = ASTOBJ_REF(iface);
+ smd->md_msg = ASTOBJ_REF(md_msg);
+ smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
+ snprintf(buf, len, "%u", smd->id);
+
+ if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
+ goto return_error;
+
+ datastore->data = smd;
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+
+ res = 0;
+
+return_error:
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
+ if (md_msg)
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+
+ if (smd && !datastore)
+ smdi_msg_datastore_destroy(smd);
+
+ if (parse)
+ ast_autoservice_stop(chan);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ int res = -1;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(id);
+ AST_APP_ARG(component);
+ );
+ char *parse;
+ struct ast_datastore *datastore = NULL;
+ struct smdi_msg_datastore *smd = NULL;
+
+ u = ast_module_user_add(chan);
+
+ if (!chan) {
+ ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
+ goto return_error;
+ }
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
+ goto return_error;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.id)) {
+ ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+ goto return_error;
+ }
+
+ if (ast_strlen_zero(args.component)) {
+ ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+ goto return_error;
+ }
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
+ ast_channel_unlock(chan);
+
+ if (!datastore) {
+ ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
+ goto return_error;
+ }
+
+ smd = datastore->data;
+
+ if (!strcasecmp(args.component, "station")) {
+ ast_copy_string(buf, smd->md_msg->fwd_st, len);
+ } else if (!strcasecmp(args.component, "callerid")) {
+ ast_copy_string(buf, smd->md_msg->calling_st, len);
+ } else if (!strcasecmp(args.component, "type")) {
+ snprintf(buf, len, "%c", smd->md_msg->type);
+ } else {
+ ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
+ args.component);
+ goto return_error;
+ }
+
+ res = 0;
+
+return_error:
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static struct ast_custom_function smdi_msg_retrieve_function = {
+ .name = "SMDI_MSG_RETRIEVE",
+ .synopsis = "Retrieve an SMDI message.",
+ .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<station>[,timeout])",
+ .desc =
+ " This function is used to retrieve an incoming SMDI message. It returns\n"
+ "an ID which can be used with the SMDI_MSG() function to access details of\n"
+ "the message. Note that this is a destructive function in the sense that\n"
+ "once an SMDI message is retrieved using this function, it is no longer in\n"
+ "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
+ "channels. The timeout for this function is optional, and the default is\n"
+ "3 seconds. When providing a timeout, it should be in milliseconds.\n"
+ "",
+ .read = smdi_msg_retrieve_read,
+};
+
+static struct ast_custom_function smdi_msg_function = {
+ .name = "SMDI_MSG",
+ .synopsis = "Retrieve details about an SMDI message.",
+ .syntax = "SMDI_MSG(<message_id>,<component>)",
+ .desc =
+ " This function is used to access details of an SMDI message that was\n"
+ "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
+ "function.\n"
+ " Valid message components are:\n"
+ " station - The forwarding station\n"
+ " callerid - The callerID of the calling party that was forwarded\n"
+ " type - The call type. The value here is the exact character\n"
+ " that came in on the SMDI link. Typically, example values\n"
+ " are: D - Direct Calls, A - Forward All Calls,\n"
+ " B - Forward Busy Calls, N - Forward No Answer Calls\n"
+ "",
+ .read = smdi_msg_read,
+};
+
static int load_module(void)
{
int res;
@@ -704,6 +1239,12 @@ static int load_module(void)
memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
+ ast_mutex_init(&mwi_monitor.lock);
+ ast_cond_init(&mwi_monitor.cond, NULL);
+
+ ast_custom_function_register(&smdi_msg_retrieve_function);
+ ast_custom_function_register(&smdi_msg_function);
+
/* load the config and start the listener threads*/
res = smdi_load(0);
if (res < 0) {
@@ -711,8 +1252,9 @@ static int load_module(void)
} else if (res == 1) {
ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
return AST_MODULE_LOAD_DECLINE;
- } else
- return AST_MODULE_LOAD_SUCCESS;
+ }
+
+ return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
@@ -721,6 +1263,18 @@ static int unload_module(void)
ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
+ destroy_all_mailbox_mappings();
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ mwi_monitor.stop = 1;
+ ast_cond_signal(&mwi_monitor.cond);
+ ast_mutex_unlock(&mwi_monitor.lock);
+
+ pthread_join(mwi_monitor.thread, NULL);
+
+ ast_custom_function_unregister(&smdi_msg_retrieve_function);
+ ast_custom_function_unregister(&smdi_msg_function);
+
return 0;
}