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$")
34 #include "asterisk/module.h"
35 #include "asterisk/lock.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/smdi.h"
38 #include "asterisk/config.h"
39 #include "asterisk/astobj.h"
40 #include "asterisk/io.h"
42 /* Message expiry time in milliseconds */
43 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
45 static const char config_file[] = "smdi.conf";
47 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
48 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
50 static void *smdi_read(void *iface_p);
51 static int smdi_load(int reload);
53 struct module_symbols *me; /* initialized in load_module() */
55 /*! \brief SMDI interface container. */
56 struct ast_smdi_interface_container {
57 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
62 * \brief Push an SMDI message to the back of an interface's message queue.
63 * \param iface a pointer to the interface to use.
64 * \param md_msg a pointer to the message to use.
66 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
68 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
73 * \brief Push an SMDI message to the back of an interface's message queue.
74 * \param iface a pointer to the interface to use.
75 * \param mwi_msg a pointer to the message to use.
77 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
79 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
83 * \brief Set the MWI indicator for a mailbox.
84 * \param iface the interface to use.
85 * \param mailbox the mailbox to use.
87 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
92 if (!(file = fopen(iface->name, "w"))) {
93 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
99 fprintf(file, "OP:MWI ");
101 for (i = 0; i < iface->msdstrip; i++)
104 fprintf(file, "%s!\x04", mailbox);
107 ASTOBJ_UNLOCK(iface);
108 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
113 * \brief Unset the MWI indicator for a mailbox.
114 * \param iface the interface to use.
115 * \param mailbox the mailbox to use.
117 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
122 if (!(file = fopen(iface->name, "w"))) {
123 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
127 ASTOBJ_WRLOCK(iface);
129 fprintf(file, "RMV:MWI ");
131 for (i = 0; i < iface->msdstrip; i++)
134 fprintf(file, "%s!\x04", mailbox);
137 ASTOBJ_UNLOCK(iface);
138 ast_debug(1, "Sent MWI unset message for %s on %s\n", mailbox, iface->name);
143 * \brief Put an SMDI message back in the front of the queue.
144 * \param iface a pointer to the interface to use.
145 * \param md_msg a pointer to the message to use.
147 * This function puts a message back in the front of the specified queue. It
148 * should be used if a message was popped but is not going to be processed for
149 * some reason, and the message needs to be returned to the queue.
151 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
153 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
157 * \brief Put an SMDI message back in the front of the queue.
158 * \param iface a pointer to the interface to use.
159 * \param mwi_msg a pointer to the message to use.
161 * This function puts a message back in the front of the specified queue. It
162 * should be used if a message was popped but is not going to be processed for
163 * some reason, and the message needs to be returned to the queue.
165 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
167 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
171 * \brief Get the next SMDI message from the queue.
172 * \param iface a pointer to the interface to use.
174 * This function pulls the first unexpired message from the SMDI message queue
175 * on the specified interface. It will purge all expired SMDI messages before
178 * \return the next SMDI message, or NULL if there were no pending messages.
180 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
182 struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
186 /* purge old messages */
189 elapsed = ast_tvdiff_ms(now, md_msg->timestamp);
191 if (elapsed > iface->msg_expiry) {
192 /* found an expired message */
193 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
194 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue. Message was %ld milliseconds too old.\n",
195 iface->name, elapsed - iface->msg_expiry);
196 md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
199 /* good message, return it */
208 * \brief Get the next SMDI message from the queue.
209 * \param iface a pointer to the interface to use.
210 * \param timeout the time to wait before returning in milliseconds.
212 * This function pulls a message from the SMDI message queue on the specified
213 * interface. If no message is available this function will wait the specified
214 * amount of time before returning.
216 * \return the next SMDI message, or NULL if there were no pending messages and
217 * the timeout has expired.
219 extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
221 struct timeval start = ast_tvnow();
223 struct ast_smdi_md_message *msg;
225 while (diff < timeout) {
227 if ((msg = ast_smdi_md_message_pop(iface)))
231 diff = ast_tvdiff_ms(ast_tvnow(), start);
234 return (ast_smdi_md_message_pop(iface));
238 * \brief Get the next SMDI message from the queue.
239 * \param iface a pointer to the interface to use.
241 * This function pulls the first unexpired message from the SMDI message queue
242 * on the specified interface. It will purge all expired SMDI messages before
245 * \return the next SMDI message, or NULL if there were no pending messages.
247 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
249 struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
250 struct timeval now = ast_tvnow();
253 /* purge old messages */
255 elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp);
257 if (elapsed > iface->msg_expiry) {
258 /* found an expired message */
259 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
260 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue. Message was %ld milliseconds too old.\n",
261 iface->name, elapsed - iface->msg_expiry);
262 mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
265 /* good message, return it */
274 * \brief Get the next SMDI message from the queue.
275 * \param iface a pointer to the interface to use.
276 * \param timeout the time to wait before returning in milliseconds.
278 * This function pulls a message from the SMDI message queue on the specified
279 * interface. If no message is available this function will wait the specified
280 * amount of time before returning.
282 * \return the next SMDI message, or NULL if there were no pending messages and
283 * the timeout has expired.
285 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
287 struct timeval start = ast_tvnow();
289 struct ast_smdi_mwi_message *msg;
291 while (diff < timeout) {
293 if ((msg = ast_smdi_mwi_message_pop(iface)))
297 diff = ast_tvdiff_ms(ast_tvnow(), start);
300 return (ast_smdi_mwi_message_pop(iface));
304 * \brief Find an SMDI interface with the specified name.
305 * \param iface_name the name/port of the interface to search for.
307 * \return a pointer to the interface located or NULL if none was found. This
308 * actually returns an ASTOBJ reference and should be released using
309 * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
311 extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
313 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
316 /*! \brief Read an SMDI message.
318 * \param iface_p the SMDI interface to read from.
320 * This function loops and reads from and SMDI interface. It must be stopped
321 * using pthread_cancel().
323 static void *smdi_read(void *iface_p)
325 struct ast_smdi_interface *iface = iface_p;
326 struct ast_smdi_md_message *md_msg;
327 struct ast_smdi_mwi_message *mwi_msg;
333 /* read an smdi message */
334 while ((c = fgetc(iface->file))) {
336 /* check if this is the start of a message */
341 else { /* Determine if this is a MD or MWI message */
342 if(c == 'D') { /* MD message */
345 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
346 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
352 /* read the message desk number */
353 for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++)
354 md_msg->mesg_desk_num[i] = fgetc(iface->file);
356 md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0';
358 /* read the message desk terminal number */
359 for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++)
360 md_msg->mesg_desk_term[i] = fgetc(iface->file);
362 md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0';
364 /* read the message type */
365 md_msg->type = fgetc(iface->file);
367 /* read the forwarding station number (may be blank) */
368 cp = &md_msg->fwd_st[0];
369 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
370 if((c = fgetc(iface->file)) == ' ') {
375 /* store c in md_msg->fwd_st */
376 if( i >= iface->msdstrip)
380 /* make sure the value is null terminated, even if this truncates it */
381 md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
384 /* read the calling station number (may be blank) */
385 cp = &md_msg->calling_st[0];
386 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
387 if (!isdigit((c = fgetc(iface->file)))) {
392 /* store c in md_msg->calling_st */
393 if (i >= iface->msdstrip)
397 /* make sure the value is null terminated, even if this truncates it */
398 md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
401 /* add the message to the message queue */
402 md_msg->timestamp = ast_tvnow();
403 ast_smdi_md_message_push(iface, md_msg);
404 ast_debug(1, "Recieved SMDI MD message on %s\n", iface->name);
406 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
408 } else if(c == 'W') { /* MWI message */
411 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
412 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
416 ASTOBJ_INIT(mwi_msg);
418 /* discard the 'I' (from 'MWI') */
421 /* read the forwarding station number (may be blank) */
422 cp = &mwi_msg->fwd_st[0];
423 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
424 if ((c = fgetc(iface->file)) == ' ') {
429 /* store c in md_msg->fwd_st */
430 if (i >= iface->msdstrip)
434 /* make sure the station number is null terminated, even if this will truncate it */
435 mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
438 /* read the mwi failure cause */
439 for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++)
440 mwi_msg->cause[i] = fgetc(iface->file);
442 mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0';
444 /* add the message to the message queue */
445 mwi_msg->timestamp = ast_tvnow();
446 ast_smdi_mwi_message_push(iface, mwi_msg);
447 ast_debug(1, "Recieved SMDI MWI message on %s\n", iface->name);
449 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
451 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
457 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
458 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
462 /*! \brief ast_smdi_md_message destructor. */
463 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
468 /*! \brief ast_smdi_mwi_message destructor. */
469 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
474 /*! \brief ast_smdi_interface destructor. */
475 void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
477 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
478 pthread_cancel(iface->thread);
479 pthread_join(iface->thread, NULL);
482 iface->thread = AST_PTHREADT_STOP;
487 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
488 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
489 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
490 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
493 ast_module_unref(ast_module_info->self);
498 * \brief Load and reload SMDI configuration.
499 * \param reload this should be 1 if we are reloading and 0 if not.
501 * This function loads/reloads the SMDI configuration and starts and stops
502 * interfaces accordingly.
504 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
506 static int smdi_load(int reload)
508 struct ast_config *conf;
509 struct ast_variable *v;
510 struct ast_smdi_interface *iface = NULL;
511 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
515 speed_t baud_rate = B9600; /* 9600 baud rate */
516 tcflag_t paritybit = PARENB; /* even parity checking */
517 tcflag_t charsize = CS7; /* seven bit characters */
518 int stopbits = 0; /* One stop bit */
520 int msdstrip = 0; /* strip zero digits */
521 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
523 if (!(conf = ast_config_load(config_file, config_flags))) {
525 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
527 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
529 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
532 /* Mark all interfaces that we are listening on. We will unmark them
533 * as we find them in the config file, this way we know any interfaces
534 * still marked after we have finished parsing the config file should
538 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
540 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
541 if (!strcasecmp(v->name, "baudrate")) {
542 if (!strcasecmp(v->value, "9600"))
544 else if(!strcasecmp(v->value, "4800"))
546 else if(!strcasecmp(v->value, "2400"))
548 else if(!strcasecmp(v->value, "1200"))
551 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
554 } else if (!strcasecmp(v->name, "msdstrip")) {
555 if (!sscanf(v->value, "%d", &msdstrip)) {
556 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
558 } else if (0 > msdstrip || msdstrip > 9) {
559 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
562 } else if (!strcasecmp(v->name, "msgexpirytime")) {
563 if (!sscanf(v->value, "%ld", &msg_expiry)) {
564 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
565 msg_expiry = SMDI_MSG_EXPIRY_TIME;
567 } else if (!strcasecmp(v->name, "paritybit")) {
568 if (!strcasecmp(v->value, "even"))
570 else if (!strcasecmp(v->value, "odd"))
571 paritybit = PARENB | PARODD;
572 else if (!strcasecmp(v->value, "none"))
575 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
578 } else if (!strcasecmp(v->name, "charsize")) {
579 if (!strcasecmp(v->value, "7"))
581 else if (!strcasecmp(v->value, "8"))
584 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
587 } else if (!strcasecmp(v->name, "twostopbits")) {
588 stopbits = ast_true(v->name);
589 } else if (!strcasecmp(v->name, "smdiport")) {
591 /* we are reloading, check if we are already
592 * monitoring this interface, if we are we do
593 * not want to start it again. This also has
594 * the side effect of not updating different
595 * setting for the serial port, but it should
596 * be trivial to rewrite this section so that
597 * options on the port are changed without
598 * restarting the interface. Or the interface
599 * could be restarted with out emptying the
601 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
602 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
603 ASTOBJ_UNMARK(iface);
604 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
609 if (!(iface = ast_calloc(1, sizeof(*iface))))
613 ASTOBJ_CONTAINER_INIT(&iface->md_q);
614 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
616 ast_copy_string(iface->name, v->value, sizeof(iface->name));
618 iface->thread = AST_PTHREADT_NULL;
620 if (!(iface->file = fopen(iface->name, "r"))) {
621 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
622 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
626 iface->fd = fileno(iface->file);
628 /* Set the proper attributes for our serial port. */
630 /* get the current attributes from the port */
631 if (tcgetattr(iface->fd, &iface->mode)) {
632 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
633 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
637 /* set the desired speed */
638 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
639 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
640 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
644 /* set the stop bits */
646 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
648 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
651 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
653 /* set the character size */
654 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
656 /* commit the desired attributes */
657 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
658 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
659 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
663 /* set the msdstrip */
664 iface->msdstrip = msdstrip;
666 /* set the message expiry time */
667 iface->msg_expiry = msg_expiry;
669 /* start the listner thread */
670 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
671 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
672 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
673 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
677 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
678 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
679 ast_module_ref(ast_module_info->self);
681 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
684 ast_config_destroy(conf);
686 /* Prune any interfaces we should no longer monitor. */
688 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
690 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
691 /* TODO: this is bad, we need an ASTOBJ method for this! */
692 if (!smdi_ifaces.head)
694 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
699 static int load_module(void)
703 /* initialize our containers */
704 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
705 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
707 /* load the config and start the listener threads*/
711 } else if (res == 1) {
712 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
713 return AST_MODULE_LOAD_DECLINE;
715 return AST_MODULE_LOAD_SUCCESS;
718 static int unload_module(void)
720 /* this destructor stops any running smdi_read threads */
721 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
722 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
727 static int reload(void)
735 } else if (res == 1) {
736 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
742 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
744 .unload = unload_module,