diff --git a/conf/autoload_configs/spandsp.conf.xml b/conf/autoload_configs/spandsp.conf.xml index aed847fe3d..682546b84a 100644 --- a/conf/autoload_configs/spandsp.conf.xml +++ b/conf/autoload_configs/spandsp.conf.xml @@ -1,5 +1,40 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/config.FS0 b/conf/config.FS0 new file mode 100644 index 0000000000..5310142148 --- /dev/null +++ b/conf/config.FS0 @@ -0,0 +1,78 @@ +CountryCode: 1 +AreaCode: 800 +FAXNumber: +1.800.555.1212 +LongDistancePrefix: 1 +InternationalPrefix: 011 +DialStringRules: etc/dialrules +ServerTracing: 0xFFF +SessionTracing: 0xFFF +RecvFileMode: 0600 +LogFileMode: 0600 +DeviceMode: 0600 +RingsBeforeAnswer: 1 +SpeakerVolume: off +GettyArgs: "-h %l dx_%s" +LocalIdentifier: "FS" +TagLineFont: etc/lutRS18.pcf +TagLineFormat: "From %%l|%c|Page %%P of %%T" +MaxRecvPages: 200 +# +# +# Modem-related stuff: should reflect modem command interface +# and hardware connection/cabling (e.g. flow control). +# +ModemType: Class1 # use this to supply a hint + +# +# Enabling this will use the hfaxd-protocol to set Caller*ID +# +#ModemSetOriginCmd: AT+VSID="%s","%d" + +# +# If "glare" during initialization becomes a problem then take +# the modem off-hook during initialization, and then place it +# back on-hook when done. +# +#ModemResetCmds: "ATH1\nAT+VCID=1" # enables CallID display +#ModemReadyCmds: ATH0 + +Class1AdaptRecvCmd: AT+FAR=1 +Class1TMConnectDelay: 400 # counteract quick CONNECT response + +# +# If you have trouble with V.17 receiving or sending, +# you may want to enable one of these, respectively. +# +#Class1RMQueryCmd: "!24,48,72,96" # enable this to disable V.17 receiving +#Class1TMQueryCmd: "!24,48,72,96" # enable this to disable V.17 sending + +# +# You'll likely want Caller*ID display (also displays DID) enabled. +# +ModemResetCmds: AT+VCID=1 # enables CallID display + +# +# The pty does not support changing parity. +# +PagerTTYParity: none + +# +# If you are "missing" Caller*ID data on some calls (but not all) +# and if you do not have adequate glare protection you may want to +# not answer based on RINGs, but rather enable the CallIDAnswerLength +# for NDID, disable AT+VCID=1 and do this: +# +#RingsBeforeAnswer: 0 +#ModemRingResponse: AT+VRID=1 + +# Uncomment DATE and TIME if you really want them, but you probably don't. +#CallIDPattern: "DATE=" +#CallIDPattern: "TIME=" +CallIDPattern: "NMBR=" +CallIDPattern: "NAME=" +CallIDPattern: "ANID=" +#CallIDPattern: "USER=" # username provided by call +#CallIDPattern: "PASS=" # password provided by call +#CallIDPattern: "CDID=" # DID context in call +CallIDPattern: "NDID=" +#CallIDAnswerLength: 4 diff --git a/configure.in b/configure.in index ece825c729..534c987a88 100644 --- a/configure.in +++ b/configure.in @@ -483,7 +483,7 @@ AC_PROG_GCC_TRADITIONAL AC_FUNC_MALLOC AC_TYPE_SIGNAL AC_FUNC_STRFTIME -AC_CHECK_FUNCS([gethostname vasprintf mmap mlock mlockall usleep getifaddrs timerfd_create getdtablesize]) +AC_CHECK_FUNCS([gethostname vasprintf mmap mlock mlockall usleep getifaddrs timerfd_create getdtablesize posix_openpt]) AC_CHECK_FUNCS([sched_setscheduler setpriority setrlimit setgroups initgroups]) AC_CHECK_FUNCS([wcsncmp setgroups asprintf setenv pselect gettimeofday localtime_r gmtime_r strcasecmp stricmp _stricmp]) @@ -494,6 +494,9 @@ AC_CHECK_LIB(rt, clock_getres, [AC_DEFINE(HAVE_CLOCK_GETRES, 1, [Define if you h AC_CHECK_LIB(rt, clock_nanosleep, [AC_DEFINE(HAVE_CLOCK_NANOSLEEP, 1, [Define if you have clock_nanosleep()])]) AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket)) +AC_CHECK_FILE(/dev/ptmx, [AC_DEFINE(HAVE_DEV_PTMX, 1, [Define if you have /dev/ptmx])]) +AC_CHECK_LIB(util, openpty, [AC_DEFINE(HAVE_OPENPTY, 1, [Define if you have openpty()])]) + AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[ #include #include ]) diff --git a/src/mod/applications/mod_spandsp/Makefile.am b/src/mod/applications/mod_spandsp/Makefile.am index 7865ce5164..2a63c42316 100644 --- a/src/mod/applications/mod_spandsp/Makefile.am +++ b/src/mod/applications/mod_spandsp/Makefile.am @@ -4,15 +4,15 @@ MODNAME=mod_spandsp TIFF_DIR=$(switch_srcdir)/libs/tiff-3.8.2 TIFF_BUILDDIR=$(switch_builddir)/libs/tiff-3.8.2 TIFF_LA=$(TIFF_BUILDDIR)/libtiff/libtiff.la - +BUILD_CFLAGS= SPANDSP_DIR=$(switch_srcdir)/libs/spandsp SPANDSP_BUILDDIR=$(switch_builddir)/libs/spandsp SPANDSP_LA=$(SPANDSP_BUILDDIR)/src/libspandsp.la mod_LTLIBRARIES = mod_spandsp.la -mod_spandsp_la_SOURCES = mod_spandsp.c udptl.c mod_spandsp_fax.c mod_spandsp_dsp.c mod_spandsp_codecs.c -mod_spandsp_la_CFLAGS = $(AM_CFLAGS) -I$(SPANDSP_DIR)/src -I$(TIFF_DIR)/libtiff -I$(SPANDSP_BUILDDIR)/src -I$(TIFF_BUILDDIR)/libtiff -I. -mod_spandsp_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(SPANDSP_LA) $(TIFF_LA) -ljpeg -lz +mod_spandsp_la_SOURCES = mod_spandsp.c udptl.c mod_spandsp_fax.c mod_spandsp_dsp.c mod_spandsp_codecs.c mod_spandsp_modem.c +mod_spandsp_la_CFLAGS = $(BUILD_CFLAGS) $(AM_CFLAGS) -I$(SPANDSP_DIR)/src -I$(TIFF_DIR)/libtiff -I$(SPANDSP_BUILDDIR)/src -I$(TIFF_BUILDDIR)/libtiff -I. +mod_spandsp_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(SPANDSP_LA) $(TIFF_LA) -ljpeg -lz -lutil mod_spandsp_la_LDFLAGS = -avoid-version -module -no-undefined -shared $(SPANDSP_LA): $(TIFF_LA) $(SPANDSP_DIR) $(SPANDSP_DIR)/.update diff --git a/src/mod/applications/mod_spandsp/mod_spandsp.c b/src/mod/applications/mod_spandsp/mod_spandsp.c index 358b055a7c..fec221e287 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp.c +++ b/src/mod/applications/mod_spandsp/mod_spandsp.c @@ -36,11 +36,14 @@ #include "mod_spandsp.h" #include +#include "mod_spandsp_modem.h" /* ************************************************************************** FREESWITCH MODULE DEFINITIONS ************************************************************************* */ +struct spandsp_globals spandsp_globals = { 0 }; + #define SPANFAX_RX_USAGE "" #define SPANFAX_TX_USAGE "" @@ -116,10 +119,9 @@ SWITCH_STANDARD_APP(spandsp_stop_fax_detect_session_function) static void event_handler(switch_event_t *event) { - mod_spandsp_fax_event_handler(event); + load_configuration(1); } - SWITCH_STANDARD_APP(t38_gateway_function) { switch_channel_t *channel = switch_core_session_get_channel(session); @@ -261,12 +263,239 @@ SWITCH_STANDARD_API(stop_tone_detect_api) return status; } + +/* ************************************************************************** + CONFIGURATION + ************************************************************************* */ + +switch_status_t load_configuration(switch_bool_t reload) +{ + switch_xml_t xml = NULL, x_lists = NULL, x_list = NULL, cfg = NULL, callprogress = NULL, xdescriptor = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + + switch_mutex_lock(spandsp_globals.mutex); + + if (spandsp_globals.tones) { + switch_core_hash_destroy(&spandsp_globals.tones); + } + + if (spandsp_globals.config_pool) { + switch_core_destroy_memory_pool(&spandsp_globals.config_pool); + } + + switch_core_new_memory_pool(&spandsp_globals.config_pool); + switch_core_hash_init(&spandsp_globals.tones, spandsp_globals.config_pool); + + spandsp_globals.modem_dialplan = "XML"; + spandsp_globals.modem_context = "default"; + spandsp_globals.modem_count = 1; + + + spandsp_globals.enable_t38 = 1; + spandsp_globals.total_sessions = 0; + spandsp_globals.verbose = 0; + spandsp_globals.use_ecm = 1; + spandsp_globals.disable_v17 = 0; + spandsp_globals.prepend_string = switch_core_strdup(spandsp_globals.config_pool, "fax"); + spandsp_globals.spool = switch_core_strdup(spandsp_globals.config_pool, "/tmp"); + spandsp_globals.ident = "SpanDSP Fax Ident"; + spandsp_globals.header = "SpanDSP Fax Header"; + + /* TODO make configuration param */ + spandsp_globals.tonedebug = 1; + + if ((xml = switch_xml_open_cfg("spandsp.conf", &cfg, NULL)) || (xml = switch_xml_open_cfg("fax.conf", &cfg, NULL))) { + status = SWITCH_STATUS_SUCCESS; + + if ((x_lists = switch_xml_child(cfg, "modem-settings"))) { + for (x_list = switch_xml_child(x_lists, "param"); x_list; x_list = x_list->next) { + const char *name = switch_xml_attr(x_list, "name"); + const char *value = switch_xml_attr(x_list, "value"); + + if (zstr(name)) { + continue; + } + + if (zstr(value)) { + continue; + } + + + if (!reload && !strcmp(name, "total-modems")) { + int tmp = atoi(value); + + if (tmp > 0 && tmp < MAX_MODEMS) { + spandsp_globals.modem_count = tmp; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid value [%d] for total-modems\n", tmp); + } + } else if (!strcmp(name, "dialplan")) { + spandsp_globals.modem_dialplan = switch_core_strdup(spandsp_globals.config_pool, value); + } else if (!strcmp(name, "context")) { + spandsp_globals.modem_context = switch_core_strdup(spandsp_globals.config_pool, value); + } else if (!strcmp(name, "verbose")) { + if (switch_true(value)) { + spandsp_globals.modem_verbose = 1; + } else { + spandsp_globals.modem_verbose = 0; + } + } + } + } + + if ((x_lists = switch_xml_child(cfg, "fax-settings")) || (x_lists = switch_xml_child(cfg, "settings"))) { + for (x_list = switch_xml_child(x_lists, "param"); x_list; x_list = x_list->next) { + const char *name = switch_xml_attr(x_list, "name"); + const char *value = switch_xml_attr(x_list, "value"); + + if (zstr(name)) { + continue; + } + + if (zstr(value)) { + continue; + } + + if (!strcmp(name, "use-ecm")) { + if (switch_true(value)) + spandsp_globals.use_ecm = 1; + else + spandsp_globals.use_ecm = 0; + } else if (!strcmp(name, "verbose")) { + if (switch_true(value)) + spandsp_globals.verbose = 1; + else + spandsp_globals.verbose = 0; + } else if (!strcmp(name, "disable-v17")) { + if (switch_true(value)) + spandsp_globals.disable_v17 = 1; + else + spandsp_globals.disable_v17 = 0; + } else if (!strcmp(name, "enable-t38")) { + if (switch_true(value)) { + spandsp_globals.enable_t38= 1; + } else { + spandsp_globals.enable_t38 = 0; + } + } else if (!strcmp(name, "enable-t38-request")) { + if (switch_true(value)) { + spandsp_globals.enable_t38_request = 1; + } else { + spandsp_globals.enable_t38_request = 0; + } + } else if (!strcmp(name, "ident")) { + spandsp_globals.ident = switch_core_strdup(spandsp_globals.config_pool, value); + } else if (!strcmp(name, "header")) { + spandsp_globals.header = switch_core_strdup(spandsp_globals.config_pool, value); + } else if (!strcmp(name, "spool-dir")) { + spandsp_globals.spool = switch_core_strdup(spandsp_globals.config_pool, value); + } else if (!strcmp(name, "file-prefix")) { + spandsp_globals.prepend_string = switch_core_strdup(spandsp_globals.config_pool, value); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unknown parameter %s\n", name); + } + + } + } + + /* Configure call progress detector */ + if ((callprogress = switch_xml_child(cfg, "descriptors"))) { + for (xdescriptor = switch_xml_child(callprogress, "descriptor"); xdescriptor; xdescriptor = switch_xml_next(xdescriptor)) { + const char *name = switch_xml_attr(xdescriptor, "name"); + const char *tone_name = NULL; + switch_xml_t tone = NULL, element = NULL; + tone_descriptor_t *descriptor = NULL; + + /* create descriptor */ + if (zstr(name)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name\n"); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding tone_descriptor: %s\n", name); + if (tone_descriptor_create(&descriptor, name, spandsp_globals.config_pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unable to allocate tone_descriptor: %s\n", name); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + switch_core_hash_insert(spandsp_globals.tones, name, descriptor); + + /* add tones to descriptor */ + for (tone = switch_xml_child(xdescriptor, "tone"); tone; tone = switch_xml_next(tone)) { + int id = 0; + tone_name = switch_xml_attr(tone, "name"); + if (zstr(tone_name)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name for %s\n", name); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + id = tone_descriptor_add_tone(descriptor, tone_name); + if (id == -1) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Unable to add tone_descriptor: %s, tone: %s. (too many tones)\n", name, tone_name); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, + "Adding tone_descriptor: %s, tone: %s(%d)\n", name, tone_name, id); + /* add elements to tone */ + for (element = switch_xml_child(tone, "element"); element; element = switch_xml_next(element)) { + const char *freq1_attr = switch_xml_attr(element, "freq1"); + const char *freq2_attr = switch_xml_attr(element, "freq2"); + const char *min_attr = switch_xml_attr(element, "min"); + const char *max_attr = switch_xml_attr(element, "max"); + int freq1, freq2, min, max; + if (zstr(freq1_attr)) { + freq1 = 0; + } else { + freq1 = atoi(freq1_attr); + } + if (zstr(freq2_attr)) { + freq2 = 0; + } else { + freq2 = atoi(freq2_attr); + } + if (zstr(min_attr)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Missing min in of %s %s(%d)\n", name, tone_name, id); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + min = atoi(min_attr); + if (zstr(max_attr)) { + max = 0; + } else { + max = atoi(max_attr); + } + /* check params */ + if ((freq1 < 0 || freq2 < 0 || min < 0 || max < 0) || (freq1 == 0 && min == 0 && max == 0)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid element param.\n"); + switch_goto_status(SWITCH_STATUS_FALSE, done); + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, + "Adding tone_descriptor: %s, tone: %s(%d), element (%d, %d, %d, %d)\n", name, tone_name, id, freq1, freq2, min, max); + tone_descriptor_add_tone_element(descriptor, id, freq1, freq2, min, max); + } + } + } + } + + done: + + switch_xml_free(xml); + } + + switch_mutex_unlock(spandsp_globals.mutex); + + return status; +} + + SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) { switch_application_interface_t *app_interface; switch_api_interface_t *api_interface; + memset(&spandsp_globals, 0, sizeof(spandsp_globals)); + spandsp_globals.pool = pool; + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + switch_mutex_init(&spandsp_globals.mutex, SWITCH_MUTEX_NESTED, pool); SWITCH_ADD_APP(app_interface, "t38_gateway", "Convert to T38 Gateway if tones are heard", "Convert to T38 Gateway if tones are heard", t38_gateway_function, "", SAF_MEDIA_TAP); @@ -284,10 +513,12 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) SWITCH_ADD_APP(app_interface, "spandsp_stop_fax_detect", "stop fax detect", "stop fax detect", spandsp_stop_fax_detect_session_function, "", SAF_NONE); + load_configuration(0); mod_spandsp_fax_load(pool); mod_spandsp_codecs_load(module_interface, pool); + if (mod_spandsp_dsp_load(module_interface, pool) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't load or process spandsp.conf, not adding tone_detect applications\n"); } else { @@ -302,6 +533,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) /* Not such severe to prevent loading */ } + modem_global_init(module_interface, pool); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "mod_spandsp loaded, using spandsp library version [%s]\n", SPANDSP_RELEASE_DATETIME_STRING); return SWITCH_STATUS_SUCCESS; @@ -313,6 +545,15 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_spandsp_shutdown) mod_spandsp_fax_shutdown(); mod_spandsp_dsp_shutdown(); + modem_global_shutdown(); + + if (spandsp_globals.tones) { + switch_core_hash_destroy(&spandsp_globals.tones); + } + + if (spandsp_globals.config_pool) { + switch_core_destroy_memory_pool(&spandsp_globals.config_pool); + } return SWITCH_STATUS_UNLOAD; } diff --git a/src/mod/applications/mod_spandsp/mod_spandsp.h b/src/mod/applications/mod_spandsp/mod_spandsp.h index 9127b39791..d803f6bf7f 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp.h +++ b/src/mod/applications/mod_spandsp/mod_spandsp.h @@ -42,9 +42,40 @@ typedef HANDLE zap_socket_t; typedef int zap_socket_t; #endif +#define MAX_MODEMS 1024 #define SPANDSP_EXPOSE_INTERNAL_STRUCTURES #include +/* The global stuff */ +struct spandsp_globals { + switch_memory_pool_t *pool; + switch_memory_pool_t *config_pool; + switch_mutex_t *mutex; + + uint32_t total_sessions; + + short int use_ecm; + short int verbose; + short int disable_v17; + short int enable_t38; + short int enable_t38_request; + short int enable_t38_insist; + char *ident; + char *header; + char *prepend_string; + char *spool; + switch_thread_cond_t *cond; + switch_mutex_t *cond_mutex; + int modem_count; + int modem_verbose; + char *modem_context; + char *modem_dialplan; + switch_hash_t *tones; + int tonedebug; +}; + +extern struct spandsp_globals spandsp_globals; + typedef enum { FUNCTION_TX, @@ -52,6 +83,37 @@ typedef enum { FUNCTION_GW } mod_spandsp_fax_application_mode_t; +/****************************************************************************** + * TONE DETECTION WITH CADENCE + */ + +#define MAX_TONES 32 +#define STRLEN 128 +/** + * Tone descriptor + * + * Defines a set of tones to look for + */ +struct tone_descriptor { + /** The name of this descriptor set */ + const char *name; + + /** Describes the tones to watch */ + super_tone_rx_descriptor_t *spandsp_tone_descriptor; + + /** The mapping of tone id to key */ + char tone_keys[MAX_TONES][STRLEN]; + int idx; + +}; +typedef struct tone_descriptor tone_descriptor_t; + + +switch_status_t tone_descriptor_create(tone_descriptor_t **descriptor, const char *name, switch_memory_pool_t *memory_pool); +int tone_descriptor_add_tone(tone_descriptor_t *descriptor, const char *name); +switch_status_t tone_descriptor_add_tone_element(tone_descriptor_t *descriptor, int tone_id, int freq1, int freq2, int min, int max); + + void mod_spandsp_fax_load(switch_memory_pool_t *pool); switch_status_t mod_spandsp_codecs_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool); switch_status_t mod_spandsp_dsp_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool); @@ -74,3 +136,5 @@ switch_status_t spandsp_fax_detect_session(switch_core_session_t *session, int hits, const char *app, const char *data, switch_tone_detect_callback_t callback); switch_status_t spandsp_fax_stop_detect_session(switch_core_session_t *session); +void spanfax_log_message(int level, const char *msg); +switch_status_t load_configuration(switch_bool_t reload); diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c index bed6c1021b..0b4ac48f29 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c +++ b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c @@ -155,48 +155,7 @@ switch_status_t spandsp_inband_dtmf_session(switch_core_session_t *session) /* private channel data */ #define TONE_PRIVATE "mod_tone_detect_bug" -/** - * Module global variables - */ -struct globals { - /** Memory pool */ - switch_memory_pool_t *pool; - /** Call progress tones mapped by descriptor name */ - switch_hash_t *tones; - /** Default debug level */ - int debug; -}; -typedef struct globals globals_t; -static globals_t globals; -/****************************************************************************** - * TONE DETECTION WITH CADENCE - */ - -#define MAX_TONES 32 -#define STRLEN 128 -/** - * Tone descriptor - * - * Defines a set of tones to look for - */ -struct tone_descriptor { - /** The name of this descriptor set */ - const char *name; - - /** Describes the tones to watch */ - super_tone_rx_descriptor_t *spandsp_tone_descriptor; - - /** The mapping of tone id to key */ - char tone_keys[MAX_TONES][STRLEN]; - int idx; - -}; -typedef struct tone_descriptor tone_descriptor_t; - -static switch_status_t tone_descriptor_create(tone_descriptor_t **descriptor, const char *name, switch_memory_pool_t *memory_pool); -static int tone_descriptor_add_tone(tone_descriptor_t *descriptor, const char *name); -static switch_status_t tone_descriptor_add_tone_element(tone_descriptor_t *descriptor, int tone_id, int freq1, int freq2, int min, int max); /** * Tone detector @@ -232,7 +191,7 @@ static switch_bool_t callprogress_detector_process_buffer(switch_media_bug_t *bu * @param memory_pool the pool to use * @return SWITCH_STATUS_SUCCESS if successful */ -static switch_status_t tone_descriptor_create(tone_descriptor_t **descriptor, const char *name, switch_memory_pool_t *memory_pool) +switch_status_t tone_descriptor_create(tone_descriptor_t **descriptor, const char *name, switch_memory_pool_t *memory_pool) { tone_descriptor_t *ldescriptor = NULL; ldescriptor = switch_core_alloc(memory_pool, sizeof(tone_descriptor_t)); @@ -253,7 +212,7 @@ static switch_status_t tone_descriptor_create(tone_descriptor_t **descriptor, co * @param key the tone key - this will be returned by the detector upon match * @return the tone ID */ -static int tone_descriptor_add_tone(tone_descriptor_t *descriptor, const char *key) +int tone_descriptor_add_tone(tone_descriptor_t *descriptor, const char *key) { int id = super_tone_rx_add_tone(descriptor->spandsp_tone_descriptor); if (id >= MAX_TONES) { @@ -279,7 +238,7 @@ static int tone_descriptor_add_tone(tone_descriptor_t *descriptor, const char *k * @param max the maximum tone duration in ms * @return SWITCH_STATUS_SUCCESS if successful */ -static switch_status_t tone_descriptor_add_tone_element(tone_descriptor_t *descriptor, int tone_id, int freq1, int freq2, int min, int max) +switch_status_t tone_descriptor_add_tone_element(tone_descriptor_t *descriptor, int tone_id, int freq1, int freq2, int min, int max) { if (super_tone_rx_add_element(descriptor->spandsp_tone_descriptor, tone_id, freq1, freq2, min, max) == 0) { return SWITCH_STATUS_SUCCESS; @@ -337,7 +296,7 @@ static switch_status_t tone_detector_create(tone_detector_t **detector, tone_des } memset(ldetector, 0, sizeof(tone_detector_t)); ldetector->descriptor = descriptor; - ldetector->debug = globals.debug; + ldetector->debug = spandsp_globals.tonedebug; *detector = ldetector; return SWITCH_STATUS_SUCCESS; } @@ -409,7 +368,7 @@ switch_status_t callprogress_detector_start(switch_core_session_t *session, cons } /* find the tone descriptor with the matching name and create the detector */ - descriptor = switch_core_hash_find(globals.tones, name); + descriptor = switch_core_hash_find(spandsp_globals.tones, name); if (!descriptor) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "(%s) no tone descriptor defined with name '%s'. Update configuration. \n", switch_channel_get_name(channel), name); return SWITCH_STATUS_FALSE; @@ -509,116 +468,11 @@ switch_status_t callprogress_detector_stop(switch_core_session_t *session) return SWITCH_STATUS_SUCCESS; } -/** - * Process configuration file - */ -static switch_status_t do_config(void) -{ - switch_status_t status = SWITCH_STATUS_SUCCESS; - switch_xml_t cfg = NULL, xml = NULL, callprogress = NULL, xdescriptor = NULL; - if (!(xml = switch_xml_open_cfg("spandsp.conf", &cfg, NULL))) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not open spandsp.conf\n"); - status = SWITCH_STATUS_FALSE; - goto done; - } - - /* TODO make configuration param */ - globals.debug = 1; - - /* Configure call progress detector */ - if ((callprogress = switch_xml_child(cfg, "descriptors"))) { - for (xdescriptor = switch_xml_child(callprogress, "descriptor"); xdescriptor; xdescriptor = switch_xml_next(xdescriptor)) { - const char *name = switch_xml_attr(xdescriptor, "name"); - const char *tone_name = NULL; - switch_xml_t tone = NULL, element = NULL; - tone_descriptor_t *descriptor = NULL; - - /* create descriptor */ - if (zstr(name)) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name\n"); - return SWITCH_STATUS_FALSE; - } - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding tone_descriptor: %s\n", name); - if (tone_descriptor_create(&descriptor, name, globals.pool) != SWITCH_STATUS_SUCCESS) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unable to allocate tone_descriptor: %s\n", name); - return SWITCH_STATUS_FALSE; - } - switch_core_hash_insert(globals.tones, name, descriptor); - - /* add tones to descriptor */ - for (tone = switch_xml_child(xdescriptor, "tone"); tone; tone = switch_xml_next(tone)) { - int id = 0; - tone_name = switch_xml_attr(tone, "name"); - if (zstr(tone_name)) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name for %s\n", name); - return SWITCH_STATUS_FALSE; - } - id = tone_descriptor_add_tone(descriptor, tone_name); - if (id == -1) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to add tone_descriptor: %s, tone: %s. (too many tones)\n", name, tone_name); - return SWITCH_STATUS_FALSE; - } - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding tone_descriptor: %s, tone: %s(%d)\n", name, tone_name, id); - /* add elements to tone */ - for (element = switch_xml_child(tone, "element"); element; element = switch_xml_next(element)) { - const char *freq1_attr = switch_xml_attr(element, "freq1"); - const char *freq2_attr = switch_xml_attr(element, "freq2"); - const char *min_attr = switch_xml_attr(element, "min"); - const char *max_attr = switch_xml_attr(element, "max"); - int freq1, freq2, min, max; - if (zstr(freq1_attr)) { - freq1 = 0; - } else { - freq1 = atoi(freq1_attr); - } - if (zstr(freq2_attr)) { - freq2 = 0; - } else { - freq2 = atoi(freq2_attr); - } - if (zstr(min_attr)) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing min in of %s %s(%d)\n", name, tone_name, id); - return SWITCH_STATUS_FALSE; - } - min = atoi(min_attr); - if (zstr(max_attr)) { - max = 0; - } else { - max = atoi(max_attr); - } - /* check params */ - if ((freq1 < 0 || freq2 < 0 || min < 0 || max < 0) || (freq1 == 0 && min == 0 && max == 0)) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid element param.\n"); - return SWITCH_STATUS_FALSE; - } - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding tone_descriptor: %s, tone: %s(%d), element (%d, %d, %d, %d)\n", name, tone_name, id, freq1, freq2, min, max); - tone_descriptor_add_tone_element(descriptor, id, freq1, freq2, min, max); - } - } - } - } - -done: - if (xml) { - switch_xml_free(xml); - } - - return status; -} - /** * Called when FreeSWITCH loads the module */ switch_status_t mod_spandsp_dsp_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) { - memset(&globals, 0, sizeof(globals_t)); - globals.pool = pool; - - switch_core_hash_init(&globals.tones, globals.pool); - if (do_config() != SWITCH_STATUS_SUCCESS) { - return SWITCH_STATUS_FALSE; - } - /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; } @@ -628,7 +482,7 @@ switch_status_t mod_spandsp_dsp_load(switch_loadable_module_interface_t **module */ void mod_spandsp_dsp_shutdown(void) { - switch_core_hash_destroy(&globals.tones); + return; } diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_fax.c b/src/mod/applications/mod_spandsp/mod_spandsp_fax.c index 5e1e90de69..8eb26a921d 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp_fax.c +++ b/src/mod/applications/mod_spandsp/mod_spandsp_fax.c @@ -60,26 +60,6 @@ typedef enum { T38_MODE_REFUSED = -1, } t38_mode_t; -/* The global stuff */ -static struct { - switch_memory_pool_t *pool; - switch_mutex_t *mutex; - - uint32_t total_sessions; - - short int use_ecm; - short int verbose; - short int disable_v17; - short int enable_t38; - short int enable_t38_request; - short int enable_t38_insist; - char ident[20]; - char header[50]; - char *prepend_string; - char *spool; - switch_thread_cond_t *cond; - switch_mutex_t *cond_mutex; -} globals; struct pvt_s { switch_core_session_t *session; @@ -128,13 +108,13 @@ static struct { static void wake_thread(int force) { if (force) { - switch_thread_cond_signal(globals.cond); + switch_thread_cond_signal(spandsp_globals.cond); return; } - if (switch_mutex_trylock(globals.cond_mutex) == SWITCH_STATUS_SUCCESS) { - switch_thread_cond_signal(globals.cond); - switch_mutex_unlock(globals.cond_mutex); + if (switch_mutex_trylock(spandsp_globals.cond_mutex) == SWITCH_STATUS_SUCCESS) { + switch_thread_cond_signal(spandsp_globals.cond); + switch_mutex_unlock(spandsp_globals.cond_mutex); } } @@ -206,7 +186,7 @@ static void *SWITCH_THREAD_FUNC timer_thread_run(switch_thread_t *thread, void * goto end; } - switch_mutex_lock(globals.cond_mutex); + switch_mutex_lock(spandsp_globals.cond_mutex); while(t38_state_list.thread_running) { @@ -214,7 +194,7 @@ static void *SWITCH_THREAD_FUNC timer_thread_run(switch_thread_t *thread, void * if (!t38_state_list.head) { switch_mutex_unlock(t38_state_list.mutex); - switch_thread_cond_wait(globals.cond, globals.cond_mutex); + switch_thread_cond_wait(spandsp_globals.cond, spandsp_globals.cond_mutex); switch_core_timer_sync(&timer); continue; } @@ -230,7 +210,7 @@ static void *SWITCH_THREAD_FUNC timer_thread_run(switch_thread_t *thread, void * switch_core_timer_next(&timer); } - switch_mutex_unlock(globals.cond_mutex); + switch_mutex_unlock(spandsp_globals.cond_mutex); end: @@ -252,9 +232,9 @@ static void launch_timer_thread(void) switch_threadattr_t *thd_attr = NULL; - switch_threadattr_create(&thd_attr, globals.pool); + switch_threadattr_create(&thd_attr, spandsp_globals.pool); switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); - switch_thread_create(&t38_state_list.thread, thd_attr, timer_thread_run, NULL, globals.pool); + switch_thread_create(&t38_state_list.thread, thd_attr, timer_thread_run, NULL, spandsp_globals.pool); } @@ -264,12 +244,12 @@ static void launch_timer_thread(void) static void counter_increment(void) { - switch_mutex_lock(globals.mutex); - globals.total_sessions++; - switch_mutex_unlock(globals.mutex); + switch_mutex_lock(spandsp_globals.mutex); + spandsp_globals.total_sessions++; + switch_mutex_unlock(spandsp_globals.mutex); } -static void spanfax_log_message(int level, const char *msg) +void spanfax_log_message(int level, const char *msg) { int fs_log_level; @@ -821,7 +801,7 @@ static t38_mode_t negotiate_t38(pvt_t *pvt) } else if ((v = switch_channel_get_variable(channel, "fax_enable_t38"))) { enabled = switch_true(v); } else { - enabled = globals.enable_t38; + enabled = spandsp_globals.enable_t38; } if (!(enabled && t38_options)) { @@ -877,7 +857,7 @@ static t38_mode_t negotiate_t38(pvt_t *pvt) if ((v = switch_channel_get_variable(channel, "fax_enable_t38_insist"))) { insist = switch_true(v); } else { - insist = globals.enable_t38_insist; + insist = spandsp_globals.enable_t38_insist; } /* This will send the options back in a response */ @@ -907,14 +887,14 @@ static t38_mode_t request_t38(pvt_t *pvt) } else if ((v = switch_channel_get_variable(channel, "fax_enable_t38"))) { enabled = switch_true(v); } else { - enabled = globals.enable_t38; + enabled = spandsp_globals.enable_t38; } if (enabled) { if ((v = switch_channel_get_variable(channel, "fax_enable_t38_request"))) { enabled = switch_true(v); } else { - enabled = globals.enable_t38_request; + enabled = spandsp_globals.enable_t38_request; } } @@ -922,7 +902,7 @@ static t38_mode_t request_t38(pvt_t *pvt) if ((v = switch_channel_get_variable(channel, "fax_enable_t38_insist"))) { insist = switch_true(v); } else { - insist = globals.enable_t38_insist; + insist = spandsp_globals.enable_t38_insist; } if ((t38_options = switch_channel_get_private(channel, "t38_options"))) { @@ -1004,19 +984,19 @@ static pvt_t *pvt_init(switch_core_session_t *session, mod_spandsp_fax_applicati if ((tmp = switch_channel_get_variable(channel, "fax_use_ecm"))) { pvt->use_ecm = switch_true(tmp); } else { - pvt->use_ecm = globals.use_ecm; + pvt->use_ecm = spandsp_globals.use_ecm; } if ((tmp = switch_channel_get_variable(channel, "fax_disable_v17"))) { pvt->disable_v17 = switch_true(tmp); } else { - pvt->disable_v17 = globals.disable_v17; + pvt->disable_v17 = spandsp_globals.disable_v17; } if ((tmp = switch_channel_get_variable(channel, "fax_verbose"))) { pvt->verbose = switch_true(tmp); } else { - pvt->verbose = globals.verbose; + pvt->verbose = spandsp_globals.verbose; } if ((tmp = switch_channel_get_variable(channel, "fax_force_caller"))) { @@ -1036,7 +1016,7 @@ static pvt_t *pvt_init(switch_core_session_t *session, mod_spandsp_fax_applicati switch_safe_free(data); } else { - pvt->ident = switch_core_session_strdup(session, globals.ident); + pvt->ident = switch_core_session_strdup(session, spandsp_globals.ident); } if ((tmp = switch_channel_get_variable(channel, "fax_header"))) { @@ -1048,7 +1028,7 @@ static pvt_t *pvt_init(switch_core_session_t *session, mod_spandsp_fax_applicati switch_safe_free(data); } else { - pvt->header = switch_core_session_strdup(session, globals.header); + pvt->header = switch_core_session_strdup(session, spandsp_globals.header); } if (pvt->app_mode == FUNCTION_TX) { @@ -1118,10 +1098,10 @@ void mod_spandsp_fax_process_fax(switch_core_session_t *session, const char *dat time = switch_time_now(); if (!(prefix = switch_channel_get_variable(channel, "fax_prefix"))) { - prefix = globals.prepend_string; + prefix = spandsp_globals.prepend_string; } - if (!(pvt->filename = switch_core_session_sprintf(session, "%s/%s-%ld-%ld.tif", globals.spool, prefix, globals.total_sessions, time))) { + if (!(pvt->filename = switch_core_session_sprintf(session, "%s/%s-%ld-%ld.tif", spandsp_globals.spool, prefix, spandsp_globals.total_sessions, time))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot automatically set fax RX destination file\n"); goto done; } @@ -1338,108 +1318,18 @@ void mod_spandsp_fax_process_fax(switch_core_session_t *session, const char *dat } } -/* ************************************************************************** - CONFIGURATION - ************************************************************************* */ - -void load_configuration(switch_bool_t reload) -{ - switch_xml_t xml = NULL, x_lists = NULL, x_list = NULL, cfg = NULL; - - if ((xml = switch_xml_open_cfg("fax.conf", &cfg, NULL))) { - if ((x_lists = switch_xml_child(cfg, "settings"))) { - for (x_list = switch_xml_child(x_lists, "param"); x_list; x_list = x_list->next) { - const char *name = switch_xml_attr(x_list, "name"); - const char *value = switch_xml_attr(x_list, "value"); - - if (zstr(name)) { - continue; - } - - if (zstr(value)) { - continue; - } - - if (!strcmp(name, "use-ecm")) { - if (switch_true(value)) - globals.use_ecm = 1; - else - globals.use_ecm = 0; - } else if (!strcmp(name, "verbose")) { - if (switch_true(value)) - globals.verbose = 1; - else - globals.verbose = 0; - } else if (!strcmp(name, "disable-v17")) { - if (switch_true(value)) - globals.disable_v17 = 1; - else - globals.disable_v17 = 0; - } else if (!strcmp(name, "enable-t38")) { - if (switch_true(value)) { - globals.enable_t38= 1; - } else { - globals.enable_t38 = 0; - } - } else if (!strcmp(name, "enable-t38-request")) { - if (switch_true(value)) { - globals.enable_t38_request = 1; - } else { - globals.enable_t38_request = 0; - } - } else if (!strcmp(name, "ident")) { - strncpy(globals.ident, value, sizeof(globals.ident) - 1); - } else if (!strcmp(name, "header")) { - strncpy(globals.header, value, sizeof(globals.header) - 1); - } else if (!strcmp(name, "spool-dir")) { - globals.spool = switch_core_strdup(globals.pool, value); - } else if (!strcmp(name, "file-prefix")) { - globals.prepend_string = switch_core_strdup(globals.pool, value); - } else { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unknown parameter %s\n", name); - } - - } - } - - switch_xml_free(xml); - } -} - -void mod_spandsp_fax_event_handler(switch_event_t *event) -{ - load_configuration(1); -} - - void mod_spandsp_fax_load(switch_memory_pool_t *pool) { uint32_t sanity = 200; - memset(&globals, 0, sizeof(globals)); memset(&t38_state_list, 0, sizeof(t38_state_list)); - globals.pool = pool; + switch_mutex_init(&spandsp_globals.mutex, SWITCH_MUTEX_NESTED, spandsp_globals.pool); + switch_mutex_init(&t38_state_list.mutex, SWITCH_MUTEX_NESTED, spandsp_globals.pool); - switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); - switch_mutex_init(&t38_state_list.mutex, SWITCH_MUTEX_NESTED, globals.pool); - - switch_mutex_init(&globals.cond_mutex, SWITCH_MUTEX_NESTED, globals.pool); - switch_thread_cond_create(&globals.cond, globals.pool); + switch_mutex_init(&spandsp_globals.cond_mutex, SWITCH_MUTEX_NESTED, spandsp_globals.pool); + switch_thread_cond_create(&spandsp_globals.cond, spandsp_globals.pool); - globals.enable_t38 = 1; - globals.total_sessions = 0; - globals.verbose = 1; - globals.use_ecm = 1; - globals.disable_v17 = 0; - globals.prepend_string = switch_core_strdup(globals.pool, "fax"); - globals.spool = switch_core_strdup(globals.pool, "/tmp"); - strncpy(globals.ident, "SpanDSP Fax Ident", sizeof(globals.ident) - 1); - strncpy(globals.header, "SpanDSP Fax Header", sizeof(globals.header) - 1); - - load_configuration(0); - - launch_timer_thread(); while(--sanity && !t38_state_list.thread_running) { @@ -1454,7 +1344,7 @@ void mod_spandsp_fax_shutdown(void) t38_state_list.thread_running = 0; wake_thread(1); switch_thread_join(&tstatus, t38_state_list.thread); - memset(&globals, 0, sizeof(globals)); + memset(&spandsp_globals, 0, sizeof(spandsp_globals)); } static const switch_state_handler_table_t t38_gateway_state_handlers; diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_modem.c b/src/mod/applications/mod_spandsp/mod_spandsp_modem.c new file mode 100644 index 0000000000..30b340d18a --- /dev/null +++ b/src/mod/applications/mod_spandsp/mod_spandsp_modem.c @@ -0,0 +1,1271 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH mod_fax. + * + * The Initial Developer of the Original Code is + * Massimo Cetra + * + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Brian West + * Anthony Minessale II + * Steve Underwood + * mod_spandsp_modem.c -- t31 Soft Modem + * + */ + +#include "mod_spandsp.h" +#include "mod_spandsp_modem.h" + +#if defined(MODEM_SUPPORT) +#include + +static struct { + int NEXT_ID; + int REF_COUNT; + int THREADCOUNT; + switch_memory_pool_t *pool; + switch_mutex_t *mutex; + modem_t MODEM_POOL[MAX_MODEMS]; + int SOFT_MAX_MODEMS; +} globals; + +struct modem_state { + int state; + char *name; +}; + +static struct modem_state MODEM_STATE[] = { + {MODEM_STATE_INIT, "INIT"}, + {MODEM_STATE_ONHOOK, "ONHOOK"}, + {MODEM_STATE_OFFHOOK, "OFFHOOK"}, + {MODEM_STATE_ACQUIRED, "ACQUIRED"}, + {MODEM_STATE_RINGING, "RINGING"}, + {MODEM_STATE_ANSWERED, "ANSWERED"}, + {MODEM_STATE_DIALING, "DIALING"}, + {MODEM_STATE_CONNECTED, "CONNECTED"}, + {MODEM_STATE_HANGUP, "HANGUP"}, + {MODEM_STATE_LAST, "UNKNOWN"} +}; + + +static modem_t *acquire_modem(int index); + + +static int t31_at_tx_handler(at_state_t *s, void *user_data, const uint8_t *buf, size_t len) +{ + modem_t *modem = user_data; + ssize_t wrote; + + wrote = write(modem->master, buf, len); + + if (wrote != len) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to pass the full buffer onto the device file. %zd bytes of %ld written: %s\n", + wrote, len, strerror(errno)); + if (wrote == -1) wrote = 0; + + if (tcflush(modem->master, TCOFLUSH)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to flush pty master buffer: %s\n", strerror(errno)); + } else if (tcflush(modem->slave, TCOFLUSH)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to flush pty slave buffer: %s\n", strerror(errno)); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Successfully flushed pty buffer\n"); + } + } + return wrote; +} + + +static int t31_call_control_handler(t31_state_t *s, void *user_data, int op, const char *num) +{ + modem_t *modem = user_data; + int ret = 0; + + if (modem->control_handler) { + ret = modem->control_handler(modem, num, op); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "DOH! NO CONTROL HANDLER INSTALLED\n"); + } + + return ret; +} + + +static modem_state_t modem_get_state(modem_t *modem) +{ + modem_state_t state; + + switch_mutex_lock(modem->mutex); + state = modem->state; + switch_mutex_unlock(modem->mutex); + + return state; +} + +static void _modem_set_state(modem_t *modem, modem_state_t state, const char *file, const char *func, int line) +{ + + switch_mutex_lock(modem->mutex); + switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_DEBUG,"Modem %s [%s] - Changing state to %s\n", modem->devlink, + modem_state2name(modem->state), modem_state2name(state)); + modem->state = state; + switch_mutex_unlock(modem->mutex); +} +#define modem_set_state(_modem, _state) _modem_set_state(_modem, _state, __FILE__, __SWITCH_FUNC__, __LINE__) + +char *modem_state2name(int state) +{ + if (state > MODEM_STATE_LAST || state < 0) { + state = MODEM_STATE_LAST; + } + + return MODEM_STATE[state].name; + +} + +int modem_close(modem_t *modem) +{ + int r = 0; + + switch_clear_flag(modem, MODEM_FLAG_RUNNING); + + if (modem->master > -1) { + shutdown(modem->master, 2); + close(modem->master); + modem->master = -1; + r++; + } + + if (modem->slave > -1) { + shutdown(modem->slave, 2); + close(modem->slave); + modem->slave = -1; + r++; + } + + + if (modem->t31_state) { + t31_free(modem->t31_state); + modem->t31_state = NULL; + } + + unlink(modem->devlink); + + switch_mutex_lock(globals.mutex); + globals.REF_COUNT--; + switch_mutex_unlock(globals.mutex); + + return r; +} + + +int modem_init(modem_t *modem, modem_control_handler_t control_handler) +{ + + memset(modem, 0, sizeof(*modem)); + + modem->master = -1; + modem->slave = -1; + + /* windows will have to try something like: + http://com0com.cvs.sourceforge.net/viewvc/com0com/com0com/ReadMe.txt?revision=RELEASED + + */ + +#if USE_OPENPTY + if (openpty(&modem->master, &modem->slave, NULL, NULL, NULL)) { + + if (modem->master < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to initialize pty\n"); + return -1; + } + + modem->stty = ttyname(modem->slave); +#else + +#if !defined(HAVE_POSIX_OPENPT) + modem->master = open("/dev/ptmx", O_RDWR); +#else + modem->master = posix_openpt(O_RDWR | O_NOCTTY); +#endif + + if (modem->master < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to initialize UNIX98 master pty\n"); + + } + + if (grantpt(modem->master) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to grant access to slave pty\n"); + + } + + if (unlockpt(modem->master) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to unlock slave pty\n"); + + } + + modem->stty = ptsname(modem->master); + + if (modem->stty == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to obtain slave pty filename\n"); + + } + + modem->slave = open(modem->stty, O_RDWR); + + if (modem->slave < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to open slave pty %s\n", modem->stty); + } + +#ifdef SOLARIS + ioctl(modem->slave, I_PUSH, "ptem"); /* push ptem */ + ioctl(modem->slave, I_PUSH, "ldterm"); /* push ldterm*/ +#endif +#endif + + modem->slot = globals.NEXT_ID++; + snprintf(modem->devlink, sizeof(modem->devlink), "/dev/FS%d", modem->slot); + + unlink(modem->devlink); + + if (symlink(modem->stty, modem->devlink)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fatal error: failed to create %s symbolic link\n", modem->devlink); + modem_close(modem); + return -1; + } + + if (fcntl(modem->master, F_SETFL, fcntl(modem->master, F_GETFL, 0) | O_NONBLOCK)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot set up non-blocking read on %s\n", ttyname(modem->master)); + modem_close(modem); + return -1; + } + + if (!(modem->t31_state = t31_init(NULL, t31_at_tx_handler, modem, t31_call_control_handler, modem, NULL, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot initialize the T.31 modem\n"); + modem_close(modem); + return -1; + + } + + if (spandsp_globals.modem_verbose) { + span_log_set_message_handler(&modem->t31_state->logging, spanfax_log_message); + span_log_set_message_handler(&modem->t31_state->audio.modems.v17_rx.logging, spanfax_log_message); + span_log_set_message_handler(&modem->t31_state->audio.modems.v29_rx.logging, spanfax_log_message); + span_log_set_message_handler(&modem->t31_state->audio.modems.v27ter_rx.logging, spanfax_log_message); + + modem->t31_state->logging.level = SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW; + modem->t31_state->audio.modems.v17_rx.logging.level = SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW; + modem->t31_state->audio.modems.v29_rx.logging.level = SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW; + modem->t31_state->audio.modems.v27ter_rx.logging.level = SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW; + } + + modem->control_handler = control_handler; + modem->flags = 0; + switch_set_flag(modem, MODEM_FLAG_RUNNING); + + switch_mutex_init(&modem->mutex, SWITCH_MUTEX_NESTED, globals.pool); + modem_set_state(modem, MODEM_STATE_INIT); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Modem [%s]->[%s] Ready\n", modem->devlink, modem->stty); + + switch_mutex_lock(globals.mutex); + globals.REF_COUNT++; + switch_mutex_unlock(globals.mutex); + + return 0; +} + +static switch_endpoint_interface_t *modem_endpoint_interface = NULL; + +struct private_object { + switch_mutex_t *mutex; + switch_core_session_t *session; + switch_channel_t *channel; + switch_codec_t read_codec; + switch_codec_t write_codec; + switch_frame_t read_frame; + unsigned char databuf[SWITCH_RECOMMENDED_BUFFER_SIZE]; + switch_timer_t timer; + modem_t *modem; + switch_caller_profile_t *caller_profile; + int dead; +}; + +typedef struct private_object private_t; + +static switch_status_t channel_on_init(switch_core_session_t *session); +static switch_status_t channel_on_hangup(switch_core_session_t *session); +static switch_status_t channel_on_destroy(switch_core_session_t *session); +static switch_status_t channel_on_routing(switch_core_session_t *session); +static switch_status_t channel_on_exchange_media(switch_core_session_t *session); +static switch_status_t channel_on_soft_execute(switch_core_session_t *session); +static switch_call_cause_t channel_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, + switch_caller_profile_t *outbound_profile, + switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags, + switch_call_cause_t *cancel_cause); +static switch_status_t channel_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id); +static switch_status_t channel_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); +static switch_status_t channel_kill_channel(switch_core_session_t *session, int sig); + + +/* + State methods they get called when the state changes to the specific state + returning SWITCH_STATUS_SUCCESS tells the core to execute the standard state method next + so if you fully implement the state you can return SWITCH_STATUS_FALSE to skip it. +*/ +static switch_status_t channel_on_init(switch_core_session_t *session) +{ + switch_channel_t *channel; + private_t *tech_pvt = NULL; + int to_ticks = 60, ring_ticks = 10, rt = ring_ticks; + int rest = 500000; + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + if (switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { + int tioflags; + char call_time[16]; + char call_date[16]; + switch_size_t retsize; + switch_time_exp_t tm; + + switch_time_exp_lt(&tm, switch_micro_time_now()); + switch_strftime(call_date, &retsize, sizeof(call_date), "%m%d", &tm); + switch_strftime(call_time, &retsize, sizeof(call_time), "%H%M", &tm); + + ioctl(tech_pvt->modem->slave, TIOCMGET, &tioflags); + tioflags |= TIOCM_RI; + ioctl(tech_pvt->modem->slave, TIOCMSET, &tioflags); + + at_reset_call_info(&tech_pvt->modem->t31_state->at_state); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "DATE", call_date); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "TIME", call_time); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "NAME", tech_pvt->caller_profile->caller_id_name); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "NMBR", tech_pvt->caller_profile->caller_id_number); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "ANID", tech_pvt->caller_profile->ani); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "USER", tech_pvt->caller_profile->username); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "CDID", tech_pvt->caller_profile->context); + at_set_call_info(&tech_pvt->modem->t31_state->at_state, "NDID", tech_pvt->caller_profile->destination_number); + + modem_set_state(tech_pvt->modem, MODEM_STATE_RINGING); + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_ALERTING); + + while(to_ticks > 0 && switch_channel_up(channel) && modem_get_state(tech_pvt->modem) == MODEM_STATE_RINGING) { + if (--rt <= 0) { + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_ALERTING); + rt = ring_ticks; + } + + switch_yield(rest); + to_ticks--; + } + + if (to_ticks < 1 || modem_get_state(tech_pvt->modem) != MODEM_STATE_ANSWERED) { + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_NO_ANSWER); + switch_channel_hangup(channel, SWITCH_CAUSE_NO_ANSWER); + } else { + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_ANSWERED); + modem_set_state(tech_pvt->modem, MODEM_STATE_CONNECTED); + switch_channel_mark_answered(channel); + } + } + + switch_channel_set_state(channel, CS_ROUTING); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_routing(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_execute(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL EXECUTE\n", switch_channel_get_name(channel)); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_destroy(switch_core_session_t *session) +{ + //switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + //channel = switch_core_session_get_channel(session); + //switch_assert(channel != NULL); + + if ((tech_pvt = switch_core_session_get_private(session))) { + + switch_core_timer_destroy(&tech_pvt->timer); + + if (tech_pvt->modem) { + *tech_pvt->modem->uuid_str = '\0'; + *tech_pvt->modem->digits = '\0'; + modem_set_state(tech_pvt->modem, MODEM_STATE_ONHOOK); + tech_pvt->modem = NULL; + } + } + + return SWITCH_STATUS_SUCCESS; +} + + +static switch_status_t channel_on_hangup(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel)); + + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_HANGUP); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_kill_channel(switch_core_session_t *session, int sig) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + switch (sig) { + case SWITCH_SIG_BREAK: + break; + case SWITCH_SIG_KILL: + tech_pvt->dead = 1; + break; + default: + break; + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL KILL\n", switch_channel_get_name(channel)); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_soft_execute(switch_core_session_t *session) +{ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL TRANSMIT\n"); + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_exchange_media(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL MODEM\n"); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_reset(switch_core_session_t *session) +{ + private_t *tech_pvt = (private_t *) switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s RESET\n", + switch_channel_get_name(switch_core_session_get_channel(session))); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_hibernate(switch_core_session_t *session) +{ + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s HIBERNATE\n", + switch_channel_get_name(switch_core_session_get_channel(session))); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_on_consume_media(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL CONSUME_MEDIA\n"); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_send_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf) +{ + private_t *tech_pvt = NULL; + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t channel_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + int r, samples_wanted, samples_read = 0; + int16_t *data; + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + if (tech_pvt->dead) return SWITCH_STATUS_FALSE; + + data = tech_pvt->read_frame.data; + samples_wanted = tech_pvt->read_codec.implementation->samples_per_packet; + + tech_pvt->read_frame.flags = SFF_NONE; + switch_core_timer_next(&tech_pvt->timer); + + do { + r = t31_tx(tech_pvt->modem->t31_state, data + samples_read, samples_wanted - samples_read); + if (r < 0) break; + samples_read += r; + } while(samples_read < samples_wanted && r > 0); + + if (r < 0) { + return SWITCH_STATUS_FALSE; + } else if (samples_read < samples_wanted) { + memset(data + samples_read, 0, sizeof(int16_t)*(samples_wanted - samples_read)); + samples_read = samples_wanted; + } + + tech_pvt->read_frame.samples = samples_read; + tech_pvt->read_frame.datalen = samples_read * 2; + + *frame = &tech_pvt->read_frame; + + return status; +} + +static switch_status_t channel_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) +{ + switch_channel_t *channel = NULL; + private_t *tech_pvt = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + if (tech_pvt->dead) return SWITCH_STATUS_FALSE; + + if (t31_rx(tech_pvt->modem->t31_state, frame->data, frame->datalen / 2)) { + status = SWITCH_STATUS_FALSE; + } + + return status; +} + +static switch_status_t channel_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) +{ + switch_channel_t *channel; + private_t *tech_pvt; + + channel = switch_core_session_get_channel(session); + switch_assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + switch (msg->message_id) { + case SWITCH_MESSAGE_INDICATE_ANSWER: + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_CONNECTED); + modem_set_state(tech_pvt->modem, MODEM_STATE_CONNECTED); + break; + case SWITCH_MESSAGE_INDICATE_PROGRESS: + t31_call_event(tech_pvt->modem->t31_state, AT_CALL_EVENT_CONNECTED); + modem_set_state(tech_pvt->modem, MODEM_STATE_CONNECTED); + break; + case SWITCH_MESSAGE_INDICATE_RINGING: + break; + case SWITCH_MESSAGE_INDICATE_BRIDGE: + break; + case SWITCH_MESSAGE_INDICATE_UNBRIDGE: + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t tech_init(private_t *tech_pvt, switch_core_session_t *session) +{ + const char *iananame = "L16"; + int rate = 8000; + int interval = 20; + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_channel_t *channel = switch_core_session_get_channel(session); + const switch_codec_implementation_t *read_impl; + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s setup codec %s/%d/%d\n", switch_channel_get_name(channel), iananame, rate, + interval); + + status = switch_core_codec_init(&tech_pvt->read_codec, + iananame, + NULL, + rate, interval, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, switch_core_session_get_pool(session)); + + if (status != SWITCH_STATUS_SUCCESS || !tech_pvt->read_codec.implementation || !switch_core_codec_ready(&tech_pvt->read_codec)) { + goto end; + } + + status = switch_core_codec_init(&tech_pvt->write_codec, + iananame, + NULL, + rate, interval, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, switch_core_session_get_pool(session)); + + + if (status != SWITCH_STATUS_SUCCESS) { + switch_core_codec_destroy(&tech_pvt->read_codec); + goto end; + } + + tech_pvt->read_frame.data = tech_pvt->databuf; + tech_pvt->read_frame.buflen = sizeof(tech_pvt->databuf); + tech_pvt->read_frame.codec = &tech_pvt->read_codec; + tech_pvt->read_frame.flags = SFF_NONE; + + switch_core_session_set_read_codec(session, &tech_pvt->read_codec); + switch_core_session_set_write_codec(session, &tech_pvt->write_codec); + + read_impl = tech_pvt->read_codec.implementation; + + switch_core_timer_init(&tech_pvt->timer, "soft", + read_impl->microseconds_per_packet / 1000, read_impl->samples_per_packet, switch_core_session_get_pool(session)); + + + switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + switch_core_session_set_private(session, tech_pvt); + tech_pvt->session = session; + tech_pvt->channel = switch_core_session_get_channel(session); + + end: + + return status; +} + +static void tech_attach(private_t *tech_pvt, modem_t *modem) +{ + tech_pvt->modem = modem; + switch_set_string(modem->uuid_str, switch_core_session_get_uuid(tech_pvt->session)); + switch_channel_set_variable_printf(tech_pvt->channel, "modem_slot", "%d", modem->slot); + switch_channel_set_variable(tech_pvt->channel, "modem_devlink", modem->devlink); + switch_channel_set_variable(tech_pvt->channel, "modem_digits", modem->digits); +} + + +static switch_call_cause_t channel_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, + switch_caller_profile_t *outbound_profile, + switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags, + switch_call_cause_t *cancel_cause) +{ + char name[128]; + switch_call_cause_t cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; + + if ((*new_session = switch_core_session_request(modem_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND, flags, pool)) != 0) { + private_t *tech_pvt; + switch_channel_t *channel; + switch_caller_profile_t *caller_profile; + char *dest = switch_core_session_strdup(*new_session, outbound_profile->destination_number); + char *modem_id_string = NULL; + char *number = NULL; + int modem_id = 0; + modem_t *modem = NULL; + + if ((modem_id_string = dest)) { + if ((number = strchr(modem_id_string, '/'))) { + *number++ = '\0'; + } + } + + if (zstr(modem_id_string) || zstr(number)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_ERROR, "Invalid dial string.\n"); + cause = SWITCH_CAUSE_INVALID_NUMBER_FORMAT; goto fail; + } + + if (!strcasecmp(modem_id_string, "a")) { + modem_id = -1; + } else { + modem_id = atoi(modem_id_string); + } + + if (!(modem = acquire_modem(modem_id))) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_ERROR, "Cannot find a modem.\n"); + cause = SWITCH_CAUSE_USER_BUSY; goto fail; + } + + switch_core_session_add_stream(*new_session, NULL); + + if ((tech_pvt = (private_t *) switch_core_session_alloc(*new_session, sizeof(private_t))) != 0) { + channel = switch_core_session_get_channel(*new_session); + switch_snprintf(name, sizeof(name), "modem/%d/%s", modem->slot, number); + switch_channel_set_name(channel, name); + + if (tech_init(tech_pvt, *new_session) != SWITCH_STATUS_SUCCESS) { + cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; goto fail; + } + + switch_set_string(modem->digits, number); + tech_attach(tech_pvt, modem); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "Hey where is my memory pool?\n"); + switch_core_session_destroy(new_session); + cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; goto fail; + } + + if (outbound_profile) { + caller_profile = switch_caller_profile_clone(*new_session, outbound_profile); + caller_profile->source = switch_core_strdup(caller_profile->pool, "mod_spandsp"); + switch_channel_set_caller_profile(channel, caller_profile); + tech_pvt->caller_profile = caller_profile; + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_ERROR, "Doh! no caller profile\n"); + cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; goto fail; + } + + switch_channel_set_state(channel, CS_INIT); + + return SWITCH_CAUSE_SUCCESS; + + fail: + + if (new_session) { + switch_core_session_destroy(new_session); + } + + if (modem) { + modem_set_state(modem, MODEM_STATE_ONHOOK); + } + } + + return cause; +} + +static switch_state_handler_table_t channel_event_handlers = { + /*.on_init */ channel_on_init, + /*.on_routing */ channel_on_routing, + /*.on_execute */ channel_on_execute, + /*.on_hangup */ channel_on_hangup, + /*.on_exchange_media */ channel_on_exchange_media, + /*.on_soft_execute */ channel_on_soft_execute, + /*.on_consume_media */ channel_on_consume_media, + /*.on_hibernate */ channel_on_hibernate, + /*.on_reset */ channel_on_reset, + /*.on_park */ NULL, + /*.on_reporting */ NULL, + /*.on_destroy */ channel_on_destroy +}; + +static switch_io_routines_t channel_io_routines = { + /*.outgoing_channel */ channel_outgoing_channel, + /*.read_frame */ channel_read_frame, + /*.write_frame */ channel_write_frame, + /*.kill_channel */ channel_kill_channel, + /*.send_dtmf */ channel_send_dtmf, + /*.receive_message */ channel_receive_message +}; + +static switch_status_t create_session(switch_core_session_t **new_session, modem_t *modem) +{ + switch_status_t status = SWITCH_STATUS_FALSE; + switch_core_session_t *session; + switch_channel_t *channel; + private_t *tech_pvt = NULL; + char name[1024]; + switch_caller_profile_t *caller_profile; + char *ani = NULL, *p, *digits = NULL; + + if (!(session = switch_core_session_request(modem_endpoint_interface, SWITCH_CALL_DIRECTION_INBOUND, SOF_NONE, NULL))) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Failure.\n"); + goto end; + } + + switch_core_session_add_stream(session, NULL); + channel = switch_core_session_get_channel(session); + tech_pvt = (private_t *) switch_core_session_alloc(session, sizeof(*tech_pvt)); + + p = switch_core_session_strdup(session, modem->digits); + + if (*p == '*') { + ani = p + 1; + if ((digits = strchr(ani, '*'))) { + *digits++ = '\0'; + } else { + ani = NULL; + } + } + + if (zstr(digits)) { + digits = p; + } + + if (zstr(ani)) { + ani = modem->devlink + 5; + } + + switch_snprintf(name, sizeof(name), "modem/%d/%s", modem->slot, digits); + switch_channel_set_name(channel, name); + + if (tech_init(tech_pvt, session) != SWITCH_STATUS_SUCCESS) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + switch_core_session_destroy(&session); + goto end; + } + + caller_profile = switch_caller_profile_new(switch_core_session_get_pool(session), + modem->devlink, + spandsp_globals.modem_dialplan, + "FSModem", + ani, + NULL, + ani, + NULL, + NULL, + "mod_spandsp", + spandsp_globals.modem_context, + digits); + + + + caller_profile->source = switch_core_strdup(caller_profile->pool, "mod_spandsp"); + switch_channel_set_caller_profile(channel, caller_profile); + tech_pvt->caller_profile = caller_profile; + switch_channel_set_state(channel, CS_INIT); + + if (switch_core_session_thread_launch(session) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error spawning thread\n"); + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end; + } + + status = SWITCH_STATUS_SUCCESS; + tech_attach(tech_pvt, modem); + *new_session = session; + + end: + + return status; +} + + +static int control_handler(modem_t *modem, const char *num, int op) +{ + switch_core_session_t *session = NULL; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Control Handler op:%d state:[%s] %s\n", + op, modem_state2name(modem_get_state(modem)), modem->devlink); + + switch (op) { + + case AT_MODEM_CONTROL_ANSWER: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - Answering\n", modem->devlink, modem_state2name(modem_get_state(modem))); + modem_set_state(modem, MODEM_STATE_ANSWERED); + break; + case AT_MODEM_CONTROL_CALL: + { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - Dialing '%s'\n", modem->devlink, modem_state2name(modem_get_state(modem)), num); + modem_set_state(modem, MODEM_STATE_DIALING); + switch_clear_flag(modem, MODEM_FLAG_XOFF); + + switch_set_string(modem->digits, num); + + if (create_session(&session, modem) != SWITCH_STATUS_SUCCESS) { + t31_call_event(modem->t31_state, AT_CALL_EVENT_HANGUP); + } else { + switch_core_session_thread_launch(session); + } + } + break; + case AT_MODEM_CONTROL_OFFHOOK: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - Going off hook\n", modem->devlink, modem_state2name(modem_get_state(modem))); + modem_set_state(modem, MODEM_STATE_OFFHOOK); + break; + case AT_MODEM_CONTROL_ONHOOK: + case AT_MODEM_CONTROL_HANGUP: + { + int set_state = 1; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - Hanging up\n", modem->devlink, modem_state2name(modem_get_state(modem))); + switch_clear_flag(modem, MODEM_FLAG_XOFF); + + modem_set_state(modem, MODEM_STATE_HANGUP); + + if (!zstr(modem->uuid_str)) { + switch_core_session_t *session; + + if ((session = switch_core_session_force_locate(modem->uuid_str))) { + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_channel_up(channel)) { + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + set_state = 0; + } + switch_core_session_rwunlock(session); + } + } + + if (set_state) { + modem_set_state(modem, MODEM_STATE_ONHOOK); + } + } + break; + case AT_MODEM_CONTROL_DTR: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - DTR %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num); + break; + case AT_MODEM_CONTROL_RTS: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - RTS %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num); + break; + case AT_MODEM_CONTROL_CTS: + { + u_char x[1]; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, + "Modem %s [%s] - CTS %s\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num ? "XON" : "XOFF"); + + if (num) { + x[0] = 0x11; + t31_at_tx_handler(&modem->t31_state->at_state, modem, x, 1); + switch_clear_flag(modem, MODEM_FLAG_XOFF); + } else { + x[0] = 0x13; + t31_at_tx_handler(&modem->t31_state->at_state, modem, x, 1); + switch_set_flag(modem, MODEM_FLAG_XOFF); + } + } + break; + case AT_MODEM_CONTROL_CAR: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - CAR %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num); + break; + case AT_MODEM_CONTROL_RNG: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - RNG %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num); + break; + case AT_MODEM_CONTROL_DSR: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - DSR %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), (int) (intptr_t) num); + break; + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "Modem %s [%s] - operation %d\n", modem->devlink, modem_state2name(modem_get_state(modem)), op); + break; + } + /*endswitch*/ + return 0; +} + +typedef enum { + MODEM_POLL_READ = (1 << 0), + MODEM_POLL_WRITE = (1 << 1), + MODEM_POLL_ERROR = (1 << 2) +} modem_poll_t; + +static int modem_wait_sock(int sock, uint32_t ms, modem_poll_t flags) +{ + struct pollfd pfds[2] = { { 0 } }; + int s = 0, r = 0; + + pfds[0].fd = sock; + + if ((flags & MODEM_POLL_READ)) { + pfds[0].events |= POLLIN; + } + + if ((flags & MODEM_POLL_WRITE)) { + pfds[0].events |= POLLOUT; + } + + if ((flags & MODEM_POLL_ERROR)) { + pfds[0].events |= POLLERR; + } + + s = poll(pfds, 1, ms); + + if (s < 0) { + r = s; + } else if (s > 0) { + if ((pfds[0].revents & POLLIN)) { + r |= MODEM_POLL_READ; + } + if ((pfds[0].revents & POLLOUT)) { + r |= MODEM_POLL_WRITE; + } + if ((pfds[0].revents & POLLERR)) { + r |= MODEM_POLL_ERROR; + } + } + + return r; + +} + +static void *SWITCH_THREAD_FUNC modem_thread(switch_thread_t *thread, void *obj) +{ + modem_t *modem = obj; + int r, avail; + char buf[T31_TX_BUF_LEN], tmp[80]; + + switch_mutex_lock(globals.mutex); + modem_init(modem, control_handler); + globals.THREADCOUNT++; + switch_mutex_unlock(globals.mutex); + + while (switch_test_flag(modem, MODEM_FLAG_RUNNING)) { + + r = modem_wait_sock(modem->master, -1, MODEM_POLL_READ | MODEM_POLL_ERROR); + + if (!switch_test_flag(modem, MODEM_FLAG_RUNNING)) { + break; + } + + if (r < 0 || !(r & MODEM_POLL_READ) || (r & MODEM_POLL_ERROR)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Bad Read on master [%s] [%d]\n", modem->devlink, r); + break; + } + + modem->last_event = switch_time_now(); + + avail = sizeof(buf) - modem->t31_state->tx.in_bytes + modem->t31_state->tx.out_bytes - 1; + + if (avail == 0 || switch_test_flag(modem, MODEM_FLAG_XOFF)) { + switch_yield(100000); + continue; + } + + r = read(modem->master, buf, avail); + + t31_at_rx(modem->t31_state, buf, r); + + memset(tmp, 0, sizeof(tmp)); + if (!strncasecmp(buf, "AT", 2)) { + int x; + strncpy(tmp, buf, r); + for(x = 0; x < r; x++) { + if(tmp[x] == '\r' || tmp[x] == '\n') { + tmp[x] = '\0'; + } + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Command on %s [%s]\n", modem->devlink, tmp); + } + } + + if (switch_test_flag(modem, MODEM_FLAG_RUNNING)) { + modem_close(modem); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Thread ended for %s\n", modem->devlink); + + switch_mutex_lock(globals.mutex); + globals.THREADCOUNT--; + switch_mutex_unlock(globals.mutex); + + return NULL; +} + +static void launch_modem_thread(modem_t *modem) +{ + switch_thread_t *thread; + switch_threadattr_t *thd_attr = NULL; + + + switch_threadattr_create(&thd_attr, globals.pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, modem_thread, modem, globals.pool); + +} + +static void activate_modems(void) +{ + int max = globals.SOFT_MAX_MODEMS; + int x; + + switch_mutex_lock(globals.mutex); + memset(globals.MODEM_POOL, 0, MAX_MODEMS); + for(x = 0; x < max; x++) { + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Starting Modem SLOT %d\n", x); + + launch_modem_thread(&globals.MODEM_POOL[x]); + } + switch_mutex_unlock(globals.mutex); +} + + +static void deactivate_modems(void) +{ + int max = globals.SOFT_MAX_MODEMS; + int x; + + switch_mutex_lock(globals.mutex); + + for(x = 0; x < max; x++) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Stopping Modem SLOT %d\n", x); + modem_close(&globals.MODEM_POOL[x]); + } + + switch_mutex_unlock(globals.mutex); + + /* Wait for Threads to die */ + while (globals.THREADCOUNT) { + switch_yield(100000); + } +} + + +static modem_t *acquire_modem(int index) +{ + modem_t *modem = NULL; + switch_time_t now = switch_time_now(); + int64_t idle_debounce = 2000000; + + switch_mutex_lock(globals.mutex); + if (index > -1 && index < globals.SOFT_MAX_MODEMS) { + modem = &globals.MODEM_POOL[index]; + } else { + int x; + + for(x = 0; x < globals.SOFT_MAX_MODEMS; x++) { + if (globals.MODEM_POOL[x].state == MODEM_STATE_ONHOOK && (now - globals.MODEM_POOL[x].last_event) > idle_debounce) { + modem = &globals.MODEM_POOL[x]; + break; + } + } + } + + if (modem && (modem->state != MODEM_STATE_ONHOOK || (now - modem->last_event) < idle_debounce)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Modem %s In Use!\n", modem->devlink); + modem = NULL; + } + + if (modem) { + modem_set_state(modem, MODEM_STATE_ACQUIRED); + modem->last_event = switch_time_now(); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No Modems Available!\n"); + } + + switch_mutex_unlock(globals.mutex); + + return modem; +} + +switch_status_t modem_global_init(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) +{ + memset(&globals, 0, sizeof(globals)); + globals.pool = pool; + globals.SOFT_MAX_MODEMS = 1; + + if (spandsp_globals.modem_count > 0) { + globals.SOFT_MAX_MODEMS = spandsp_globals.modem_count; + } + + switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool); + + modem_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE); + modem_endpoint_interface->interface_name = "modem"; + modem_endpoint_interface->io_routines = &channel_io_routines; + modem_endpoint_interface->state_handler = &channel_event_handlers; + + activate_modems(); + + return SWITCH_STATUS_SUCCESS; + +} + +void modem_global_shutdown(void) +{ + deactivate_modems(); +} + +#else + +void modem_global_init(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Modem support disabled\n"); +} + +void modem_global_shutdown(void) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Modem support disabled\n"); +} + +#endif diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_modem.h b/src/mod/applications/mod_spandsp/mod_spandsp_modem.h new file mode 100644 index 0000000000..7e89a9edc7 --- /dev/null +++ b/src/mod/applications/mod_spandsp/mod_spandsp_modem.h @@ -0,0 +1,111 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH mod_fax. + * + * The Initial Developer of the Original Code is + * Massimo Cetra + * + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Brian West + * Anthony Minessale II + * Steve Underwood + * mod_spandsp_modem.h -- Fax modem applications provided by SpanDSP + * + */ + +#include "switch_private.h" +#if defined(HAVE_OPENPTY) || defined(HAVE_DEV_PTMX) || defined(HAVE_POSIX_OPENPT) +#define MODEM_SUPPORT 1 +#if !defined(HAVE_POSIX_OPENPT) && !defined(HAVE_DEV_PTMX) +#define USE_OPENPTY 1 +#endif +#ifndef _MOD_SPANDSP_MODEM_H +#define _MOD_SPANDSP_MODEM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef enum { + MODEM_STATE_INIT, + MODEM_STATE_ONHOOK, + MODEM_STATE_OFFHOOK, + MODEM_STATE_ACQUIRED, + MODEM_STATE_RINGING, + MODEM_STATE_ANSWERED, + MODEM_STATE_DIALING, + MODEM_STATE_CONNECTED, + MODEM_STATE_HANGUP, + MODEM_STATE_LAST +} modem_state_t; + +struct modem; + +typedef int (*modem_control_handler_t)(struct modem *, const char *, int); + + +typedef enum { + MODEM_FLAG_RUNNING = ( 1 << 0), + MODEM_FLAG_XOFF = ( 1 << 1) +} modem_flags; + +struct modem { + t31_state_t *t31_state; + char digits[512]; + modem_flags flags; + int master; + int slave; + char *stty; + char devlink[128]; + int id; + modem_state_t state; + modem_control_handler_t control_handler; + void *user_data; + switch_mutex_t *mutex; + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; + switch_time_t last_event; + int slot; +}; + +typedef struct modem modem_t; + +char *modem_state2name(int state); +int modem_close(struct modem *fm); +int modem_init(struct modem *fm, modem_control_handler_t control_handler); + +#endif //MODEM_SUPPORT + +switch_status_t modem_global_init(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool); +void modem_global_shutdown(void); + +#endif //_MOD_SPANDSP_MODEM_H