2 * Asterisk -- A telephony toolkit for Linux.
4 * Copyright (C) 2005-2006, Digium, Inc.
6 * Matthew A. Nicholson <mnicholson@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief SMDI support for Asterisk.
22 * \author Matthew A. Nicholson <mnicholson@digium.com>
27 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37 #include "asterisk/module.h"
38 #include "asterisk/lock.h"
39 #include "asterisk/utils.h"
40 #include "asterisk/smdi.h"
41 #include "asterisk/config.h"
42 #include "asterisk/astobj.h"
43 #include "asterisk/io.h"
44 #include "asterisk/logger.h"
45 #include "asterisk/options.h"
47 /* Message expiry time in milliseconds */
48 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
50 static const char config_file[] = "smdi.conf";
52 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
53 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
55 static void *smdi_read(void *iface_p);
56 static int smdi_load(int reload);
58 struct module_symbols *me; /* initialized in load_module() */
60 /*! \brief SMDI interface container. */
61 struct ast_smdi_interface_container {
62 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
67 * \brief Push an SMDI message to the back of an interface's message queue.
68 * \param iface a pointer to the interface to use.
69 * \param md_msg a pointer to the message to use.
71 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
73 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
78 * \brief Push an SMDI message to the back of an interface's message queue.
79 * \param iface a pointer to the interface to use.
80 * \param mwi_msg a pointer to the message to use.
82 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
84 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
88 * \brief Set the MWI indicator for a mailbox.
89 * \param iface the interface to use.
90 * \param mailbox the mailbox to use.
92 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
97 file = fopen(iface->name, "w");
99 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
103 ASTOBJ_WRLOCK(iface);
105 fprintf(file, "OP:MWI ");
107 for(i = 0; i < iface->msdstrip; i++)
110 fprintf(file, "%s!\x04", mailbox);
113 ASTOBJ_UNLOCK(iface);
115 ast_log(LOG_DEBUG, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
120 * \brief Unset the MWI indicator for a mailbox.
121 * \param iface the interface to use.
122 * \param mailbox the mailbox to use.
124 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
129 file = fopen(iface->name, "w");
131 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
135 ASTOBJ_WRLOCK(iface);
137 fprintf(file, "RMV:MWI ");
139 for(i = 0; i < iface->msdstrip; i++)
142 fprintf(file, "%s!\x04", mailbox);
145 ASTOBJ_UNLOCK(iface);
147 ast_log(LOG_DEBUG, "Sent MWI unset message for %s on %s\n", mailbox, iface->name);
152 * \brief Put an SMDI message back in the front of the queue.
153 * \param iface a pointer to the interface to use.
154 * \param md_msg a pointer to the message to use.
156 * This function puts a message back in the front of the specified queue. It
157 * should be used if a message was popped but is not going to be processed for
158 * some reason, and the message needs to be returned to the queue.
160 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
162 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
166 * \brief Put an SMDI message back in the front of the queue.
167 * \param iface a pointer to the interface to use.
168 * \param mwi_msg a pointer to the message to use.
170 * This function puts a message back in the front of the specified queue. It
171 * should be used if a message was popped but is not going to be processed for
172 * some reason, and the message needs to be returned to the queue.
174 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
176 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
180 * \brief Get the next SMDI message from the queue.
181 * \param iface a pointer to the interface to use.
183 * This function pulls the first unexpired message from the SMDI message queue
184 * on the specified interface. It will purge all expired SMDI messages before
187 * \return the next SMDI message, or NULL if there were no pending messages.
189 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
191 struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
195 /* purge old messages */
198 elapsed = ast_tvdiff_ms(now, md_msg->timestamp);
200 if (elapsed > iface->msg_expiry) {
201 /* found an expired message */
202 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
203 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue. Message was %ld milliseconds too old.\n",
204 iface->name, elapsed - iface->msg_expiry);
205 md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
208 /* good message, return it */
217 * \brief Get the next SMDI message from the queue.
218 * \param iface a pointer to the interface to use.
219 * \param timeout the time to wait before returning in milliseconds.
221 * This function pulls a message from the SMDI message queue on the specified
222 * interface. If no message is available this function will wait the specified
223 * amount of time before returning.
225 * \return the next SMDI message, or NULL if there were no pending messages and
226 * the timeout has expired.
228 extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
230 struct timeval start;
232 struct ast_smdi_md_message *msg;
235 while (diff < timeout) {
237 if ((msg = ast_smdi_md_message_pop(iface)))
241 diff = ast_tvdiff_ms(ast_tvnow(), start);
244 return (ast_smdi_md_message_pop(iface));
248 * \brief Get the next SMDI message from the queue.
249 * \param iface a pointer to the interface to use.
251 * This function pulls the first unexpired message from the SMDI message queue
252 * on the specified interface. It will purge all expired SMDI messages before
255 * \return the next SMDI message, or NULL if there were no pending messages.
257 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
259 struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
263 /* purge old messages */
266 elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp);
268 if (elapsed > iface->msg_expiry) {
269 /* found an expired message */
270 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
271 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue. Message was %ld milliseconds too old.\n",
272 iface->name, elapsed - iface->msg_expiry);
273 mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
276 /* good message, return it */
285 * \brief Get the next SMDI message from the queue.
286 * \param iface a pointer to the interface to use.
287 * \param timeout the time to wait before returning in milliseconds.
289 * This function pulls a message from the SMDI message queue on the specified
290 * interface. If no message is available this function will wait the specified
291 * amount of time before returning.
293 * \return the next SMDI message, or NULL if there were no pending messages and
294 * the timeout has expired.
296 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
298 struct timeval start;
300 struct ast_smdi_mwi_message *msg;
303 while (diff < timeout) {
305 if ((msg = ast_smdi_mwi_message_pop(iface)))
309 diff = ast_tvdiff_ms(ast_tvnow(), start);
312 return (ast_smdi_mwi_message_pop(iface));
316 * \brief Find an SMDI interface with the specified name.
317 * \param iface_name the name/port of the interface to search for.
319 * \return a pointer to the interface located or NULL if none was found. This
320 * actually returns an ASTOBJ reference and should be released using
321 * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
323 extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
325 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
328 /*! \brief Read an SMDI message.
330 * \param iface_p the SMDI interface to read from.
332 * This function loops and reads from and SMDI interface. It must be stopped
333 * using pthread_cancel().
335 static void *smdi_read(void *iface_p)
337 struct ast_smdi_interface *iface = iface_p;
338 struct ast_smdi_md_message *md_msg;
339 struct ast_smdi_mwi_message *mwi_msg;
345 /* read an smdi message */
346 while ((c = fgetc(iface->file))) {
348 /* check if this is the start of a message */
353 else { /* Determine if this is a MD or MWI message */
354 if(c == 'D') { /* MD message */
357 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
358 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
364 /* read the message desk number */
365 for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++)
366 md_msg->mesg_desk_num[i] = fgetc(iface->file);
368 md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0';
370 /* read the message desk terminal number */
371 for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++)
372 md_msg->mesg_desk_term[i] = fgetc(iface->file);
374 md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0';
376 /* read the message type */
377 md_msg->type = fgetc(iface->file);
379 /* read the forwarding station number (may be blank) */
380 cp = &md_msg->fwd_st[0];
381 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
382 if((c = fgetc(iface->file)) == ' ') {
387 /* store c in md_msg->fwd_st */
388 if( i >= iface->msdstrip)
392 /* make sure the value is null terminated, even if this truncates it */
393 md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
396 /* read the calling station number (may be blank) */
397 cp = &md_msg->calling_st[0];
398 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
399 if (!isdigit((c = fgetc(iface->file)))) {
404 /* store c in md_msg->calling_st */
405 if (i >= iface->msdstrip)
409 /* make sure the value is null terminated, even if this truncates it */
410 md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
413 /* add the message to the message queue */
414 md_msg->timestamp = ast_tvnow();
415 ast_smdi_md_message_push(iface, md_msg);
417 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
419 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
421 } else if(c == 'W') { /* MWI message */
424 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
425 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
429 ASTOBJ_INIT(mwi_msg);
431 /* discard the 'I' (from 'MWI') */
434 /* read the forwarding station number (may be blank) */
435 cp = &mwi_msg->fwd_st[0];
436 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
437 if ((c = fgetc(iface->file)) == ' ') {
442 /* store c in md_msg->fwd_st */
443 if (i >= iface->msdstrip)
447 /* make sure the station number is null terminated, even if this will truncate it */
448 mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
451 /* read the mwi failure cause */
452 for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++)
453 mwi_msg->cause[i] = fgetc(iface->file);
455 mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0';
457 /* add the message to the message queue */
458 mwi_msg->timestamp = ast_tvnow();
459 ast_smdi_mwi_message_push(iface, mwi_msg);
461 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
463 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
465 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
471 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
472 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
476 /*! \brief ast_smdi_md_message destructor. */
477 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
482 /*! \brief ast_smdi_mwi_message destructor. */
483 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
488 /*! \brief ast_smdi_interface destructor. */
489 void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
491 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
492 pthread_cancel(iface->thread);
493 pthread_join(iface->thread, NULL);
496 iface->thread = AST_PTHREADT_STOP;
501 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
502 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
503 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
504 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
507 ast_module_unref(ast_module_info->self);
512 * \brief Load and reload SMDI configuration.
513 * \param reload this should be 1 if we are reloading and 0 if not.
515 * This function loads/reloads the SMDI configuration and starts and stops
516 * interfaces accordingly.
518 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
520 static int smdi_load(int reload)
522 struct ast_config *conf;
523 struct ast_variable *v;
524 struct ast_smdi_interface *iface = NULL;
528 speed_t baud_rate = B9600; /* 9600 baud rate */
529 tcflag_t paritybit = PARENB; /* even parity checking */
530 tcflag_t charsize = CS7; /* seven bit characters */
531 int stopbits = 0; /* One stop bit */
533 int msdstrip = 0; /* strip zero digits */
534 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
536 conf = ast_config_load(config_file);
540 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
542 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
546 /* Mark all interfaces that we are listening on. We will unmark them
547 * as we find them in the config file, this way we know any interfaces
548 * still marked after we have finished parsing the config file should
552 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
554 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
555 if (!strcasecmp(v->name, "baudrate")) {
556 if (!strcasecmp(v->value, "9600"))
558 else if(!strcasecmp(v->value, "4800"))
560 else if(!strcasecmp(v->value, "2400"))
562 else if(!strcasecmp(v->value, "1200"))
565 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
568 } else if (!strcasecmp(v->name, "msdstrip")) {
569 if (!sscanf(v->value, "%d", &msdstrip)) {
570 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
572 } else if (0 > msdstrip || msdstrip > 9) {
573 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
576 } else if (!strcasecmp(v->name, "msgexpirytime")) {
577 if (!sscanf(v->value, "%ld", &msg_expiry)) {
578 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
579 msg_expiry = SMDI_MSG_EXPIRY_TIME;
581 } else if (!strcasecmp(v->name, "paritybit")) {
582 if (!strcasecmp(v->value, "even"))
584 else if (!strcasecmp(v->value, "odd"))
585 paritybit = PARENB | PARODD;
586 else if (!strcasecmp(v->value, "none"))
589 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
592 } else if (!strcasecmp(v->name, "charsize")) {
593 if (!strcasecmp(v->value, "7"))
595 else if (!strcasecmp(v->value, "8"))
598 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
601 } else if (!strcasecmp(v->name, "twostopbits")) {
602 stopbits = ast_true(v->name);
603 } else if (!strcasecmp(v->name, "smdiport")) {
605 /* we are reloading, check if we are already
606 * monitoring this interface, if we are we do
607 * not want to start it again. This also has
608 * the side effect of not updating different
609 * setting for the serial port, but it should
610 * be trivial to rewrite this section so that
611 * options on the port are changed without
612 * restarting the interface. Or the interface
613 * could be restarted with out emptying the
615 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
616 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
617 ASTOBJ_UNMARK(iface);
618 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
623 if (!(iface = ast_calloc(1, sizeof(*iface))))
627 ASTOBJ_CONTAINER_INIT(&iface->md_q);
628 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
630 ast_copy_string(iface->name, v->value, sizeof(iface->name));
632 if (!(iface->file = fopen(iface->name, "r"))) {
633 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
634 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
638 iface->fd = fileno(iface->file);
640 /* Set the proper attributes for our serial port. */
642 /* get the current attributes from the port */
643 if (tcgetattr(iface->fd, &iface->mode)) {
644 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
645 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
649 /* set the desired speed */
650 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
651 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
652 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
656 /* set the stop bits */
658 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
660 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
663 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
665 /* set the character size */
666 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
668 /* commit the desired attributes */
669 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
670 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
671 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
675 /* set the msdstrip */
676 iface->msdstrip = msdstrip;
678 /* set the message expiry time */
679 iface->msg_expiry = msg_expiry;
681 /* start the listner thread */
682 if (option_verbose > 2)
683 ast_verbose(VERBOSE_PREFIX_3 "Starting SMDI monitor thread for %s\n", iface->name);
684 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
685 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
686 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
690 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
691 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
692 ast_module_ref(ast_module_info->self);
694 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
697 ast_config_destroy(conf);
699 /* Prune any interfaces we should no longer monitor. */
701 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
703 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
704 /* TODO: this is bad, we need an ASTOBJ method for this! */
705 if (!smdi_ifaces.head)
707 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
712 static int load_module(void)
716 /* initialize our containers */
717 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
718 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
720 /* load the config and start the listener threads*/
724 } else if (res == 1) {
725 ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SDMI listener.\n");
726 return AST_MODULE_LOAD_DECLINE;;
731 static int unload_module(void)
733 /* this destructor stops any running smdi_read threads */
734 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
735 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
740 static int reload(void)
748 } else if (res == 1) {
749 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
755 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
757 .unload = unload_module,