diff --git a/configure.in b/configure.in index 838c1087a3..b19303d6df 100644 --- a/configure.in +++ b/configure.in @@ -869,32 +869,33 @@ fi CHECK_ERLANG AC_CONFIG_FILES([Makefile - build/Makefile - src/Makefile - src/mod/Makefile - src/mod/applications/mod_enum/Makefile + build/Makefile + src/Makefile + src/mod/Makefile + src/mod/applications/mod_enum/Makefile src/mod/applications/mod_expr/Makefile + src/mod/applications/mod_fax/Makefile src/mod/applications/mod_stress/Makefile src/mod/applications/mod_t38gateway/Makefile src/mod/endpoints/mod_portaudio/Makefile src/mod/endpoints/mod_skinny/Makefile src/mod/endpoints/mod_skypopen/Makefile - src/mod/endpoints/mod_sofia/Makefile + src/mod/endpoints/mod_sofia/Makefile src/mod/formats/mod_portaudio_stream/Makefile - src/mod/asr_tts/mod_unimrcp/Makefile - src/mod/languages/mod_java/Makefile + src/mod/asr_tts/mod_unimrcp/Makefile + src/mod/languages/mod_java/Makefile src/mod/languages/mod_lua/Makefile - src/mod/languages/mod_python/Makefile + src/mod/languages/mod_python/Makefile src/mod/languages/mod_spidermonkey/Makefile - src/mod/event_handlers/mod_erlang_event/Makefile - src/include/switch_am_config.h - build/getsounds.sh - build/getlib.sh + src/mod/event_handlers/mod_erlang_event/Makefile + src/include/switch_am_config.h + build/getsounds.sh + build/getlib.sh build/freeswitch.pc - build/modmake.rules - libs/xmlrpc-c/include/xmlrpc-c/config.h - libs/xmlrpc-c/xmlrpc_config.h - scripts/gentls_cert]) + build/modmake.rules + libs/xmlrpc-c/include/xmlrpc-c/config.h + libs/xmlrpc-c/xmlrpc_config.h + scripts/gentls_cert]) AM_CONDITIONAL(ISLINUX, [test `uname -s` = Linux]) AM_CONDITIONAL(ISMAC, [test `uname -s` = Darwin]) diff --git a/src/include/switch_rtp.h b/src/include/switch_rtp.h index 8e0693cdc1..7e6028a68b 100644 --- a/src/include/switch_rtp.h +++ b/src/include/switch_rtp.h @@ -176,6 +176,8 @@ SWITCH_DECLARE(switch_port_t) switch_rtp_get_remote_port(switch_rtp_t *rtp_sessi SWITCH_DECLARE(void) switch_rtp_reset_media_timer(switch_rtp_t *rtp_session); SWITCH_DECLARE(void) switch_rtp_set_max_missed_packets(switch_rtp_t *rtp_session, uint32_t max); +SWITCH_DECLARE(switch_status_t) switch_rtp_udptl_mode(switch_rtp_t *rtp_session); + /*! \brief Assign a local address to the RTP session \param rtp_session an RTP session to assign the local address to diff --git a/src/include/switch_types.h b/src/include/switch_types.h index a3a9599b62..0467895e8c 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -499,7 +499,6 @@ typedef enum { SWITCH_RTP_FLAG_GOOGLEHACK - Convert payload from 102 to 97 SWITCH_RTP_FLAG_VAD - Enable VAD SWITCH_RTP_FLAG_BREAK - Stop what you are doing and return SWITCH_STATUS_BREAK - SWITCH_RTP_FLAG_MINI - Use mini RTP when possible SWITCH_RTP_FLAG_DATAWAIT - Do not return from reads unless there is data even when non blocking SWITCH_RTP_FLAG_BUGGY_2833 - Emulate the bug in cisco equipment to allow interop SWITCH_RTP_FLAG_PASS_RFC2833 - Pass 2833 (ignore it) @@ -518,7 +517,7 @@ typedef enum { SWITCH_RTP_FLAG_GOOGLEHACK = (1 << 8), SWITCH_RTP_FLAG_VAD = (1 << 9), SWITCH_RTP_FLAG_BREAK = (1 << 10), - SWITCH_RTP_FLAG_MINI = (1 << 11), + SWITCH_RTP_FLAG_UDPTL = (1 << 11), SWITCH_RTP_FLAG_DATAWAIT = (1 << 12), SWITCH_RTP_FLAG_BUGGY_2833 = (1 << 13), SWITCH_RTP_FLAG_PASS_RFC2833 = (1 << 14), @@ -725,10 +724,13 @@ typedef enum { SWITCH_MESSAGE_INDICATE_APPLICATION_EXEC, SWITCH_MESSAGE_INDICATE_APPLICATION_EXEC_COMPLETE, SWITCH_MESSAGE_INDICATE_PHONE_EVENT, + SWITCH_MESSAGE_INDICATE_T38_DESCRIPTION, + SWITCH_MESSAGE_INDICATE_UDPTL_MODE, SWITCH_MESSAGE_INVALID } switch_core_session_message_types_t; typedef struct { + uint16_t T38FaxVersion; uint32_t T38MaxBitRate; switch_bool_t T38FaxFillBitRemoval; switch_bool_t T38FaxTranscodingMMR; @@ -1008,7 +1010,8 @@ typedef enum { typedef enum { - CF_APP_TAGGED = (1 << 0) + CF_APP_TAGGED = (1 << 0), + CF_APP_T38 = (1 << 1) } switch_channel_app_flag_t; @@ -1034,7 +1037,8 @@ typedef enum { SFF_RFC2833 = (1 << 4), SFF_PROXY_PACKET = (1 << 5), SFF_DYNAMIC = (1 << 6), - SFF_ZRTP = (1 << 7) + SFF_ZRTP = (1 << 7), + SFF_UDPTL_PACKET = (1 << 8) } switch_frame_flag_enum_t; typedef uint32_t switch_frame_flag_t; diff --git a/src/mod/.gitignore b/src/mod/.gitignore index 9961a54104..e26a2619d6 100644 --- a/src/mod/.gitignore +++ b/src/mod/.gitignore @@ -1,5 +1,7 @@ /Makefile /Makefile.in +/applications/mod_fax/Makefile +/applications/mod_fax/Makefile.in /applications/mod_commands/Makefile /applications/mod_conference/Makefile /applications/mod_dptools/Makefile diff --git a/src/mod/applications/mod_fax/Makefile b/src/mod/applications/mod_fax/Makefile deleted file mode 100644 index d4609e80bd..0000000000 --- a/src/mod/applications/mod_fax/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -BASE=../../../.. - -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 - -SPANDSP_DIR=$(switch_srcdir)/libs/spandsp -SPANDSP_BUILDDIR=$(switch_builddir)/libs/spandsp -SPANDSP_LA=$(SPANDSP_BUILDDIR)/src/libspandsp.la - -LOCAL_CFLAGS=-I$(SPANDSP_DIR)/src -I$(TIFF_DIR)/libtiff -I$(SPANDSP_BUILDDIR)/src -I$(TIFF_BUILDDIR)/libtiff -LOCAL_LIBADD=$(SPANDSP_LA) $(TIFF_LA) -LOCAL_LDFLAGS=-ljpeg - -include $(BASE)/build/modmake.rules -$(MODNAME).lo: $(SPANDSP_LA) $(TIFF_LA) - -$(SPANDSP_LA): $(TIFF_LA) $(SPANDSP_DIR) $(SPANDSP_DIR)/.update - cd $(SPANDSP_BUILDDIR) && $(MAKE) -j1 - $(TOUCH_TARGET) - -$(TIFF_LA): $(TIFF_DIR) $(TIFF_DIR)/.update - cd $(TIFF_BUILDDIR) && $(MAKE) -j1 - $(TOUCH_TARGET) - - diff --git a/src/mod/applications/mod_fax/Makefile.am b/src/mod/applications/mod_fax/Makefile.am new file mode 100644 index 0000000000..10babdd75f --- /dev/null +++ b/src/mod/applications/mod_fax/Makefile.am @@ -0,0 +1,17 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_fax + +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 + +SPANDSP_DIR=$(switch_srcdir)/libs/spandsp +SPANDSP_BUILDDIR=$(switch_builddir)/libs/spandsp +SPANDSP_LA=$(SPANDSP_BUILDDIR)/src/libspandsp.la + +mod_LTLIBRARIES = mod_fax.la +mod_fax_la_SOURCES = mod_fax.c ../mod_t38gateway/udptl.c +mod_fax_la_CFLAGS = $(AM_CFLAGS) -I$(SPANDSP_DIR)/src -I$(TIFF_DIR)/libtiff -I$(SPANDSP_BUILDDIR)/src -I$(TIFF_BUILDDIR)/libtiff -I../mod_t38gateway +mod_fax_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(SPANDSP_LA) $(TIFF_LA) +mod_fax_la_LDFLAGS = -avoid-version -module -no-undefined -shared -ljpeg + diff --git a/src/mod/applications/mod_fax/mod_fax.c b/src/mod/applications/mod_fax/mod_fax.c index 185a341d3a..dbe018ab1f 100644 --- a/src/mod/applications/mod_fax/mod_fax.c +++ b/src/mod/applications/mod_fax/mod_fax.c @@ -23,7 +23,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * + * * Brian West * Anthony Minessale II * Steve Underwood @@ -38,20 +38,34 @@ #include #include +#include "udptl.h" + +#define LOCAL_FAX_MAX_DATAGRAM 400 +#define MAX_FEC_ENTRIES 4 +#define MAX_FEC_SPAN 4 + /***************************************************************************** OUR DEFINES AND STRUCTS *****************************************************************************/ typedef enum { FUNCTION_TX, - FUNCTION_RX + FUNCTION_RX, + FUNCTION_GW } application_mode_t; typedef enum { T38_MODE, - AUDIO_MODE + AUDIO_MODE, + T38_GATEWAY_MODE } transport_mode_t; +typedef enum { + T38_MODE_UNKNOWN = 0, + T38_MODE_NEGOTIATED = 1, + T38_MODE_REQUESTED = 2, + T38_MODE_REFUSED = -1, +} t38_mode_t; /* The global stuff */ static struct { @@ -63,6 +77,9 @@ static struct { 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; @@ -76,6 +93,10 @@ struct pvt_s { fax_state_t *fax_state; t38_terminal_state_t *t38_state; + t38_gateway_state_t *t38_gateway_state; + t38_core_state_t *t38_core; + + udptl_state_t *udptl_state; char *filename; char *ident; @@ -90,15 +111,138 @@ struct pvt_s { int tx_page_end; int done; + + t38_mode_t t38_mode; - /* UNUSED AT THE MOMENT - int enable_t38_reinvite; - */ - + struct pvt_s *next; }; typedef struct pvt_s pvt_t; +static void launch_timer_thread(void); + +static struct { + pvt_t *head; + switch_mutex_t *mutex; + switch_thread_t *thread; + int thread_running; +} t38_state_list; + +static int add_pvt(pvt_t *pvt) +{ + int r = 0; + uint32_t sanity = 50; + + switch_mutex_lock(t38_state_list.mutex); + if (!t38_state_list.thread_running) { + + launch_timer_thread(); + + while(--sanity && !t38_state_list.thread_running) { + switch_yield(10000); + } + } + switch_mutex_unlock(t38_state_list.mutex); + + if (t38_state_list.thread_running) { + switch_mutex_lock(t38_state_list.mutex); + pvt->next = t38_state_list.head; + t38_state_list.head = pvt; + switch_mutex_unlock(t38_state_list.mutex); + } + + return r; + +} + + +static int del_pvt(pvt_t *del_pvt) +{ + pvt_t *p, *l = NULL; + int r = 0; + + if (!t38_state_list.thread_running) goto end; + + switch_mutex_lock(t38_state_list.mutex); + for (p = t38_state_list.head; p; p = p->next) { + if (p == del_pvt) { + if (l) { + l->next = p->next; + } else { + t38_state_list.head = p->next; + } + p->next = NULL; + r = 1; + goto end; + } + + l = p; + } + + end: + + switch_mutex_unlock(t38_state_list.mutex); + + return r; + +} + +static void *SWITCH_THREAD_FUNC timer_thread_run(switch_thread_t *thread, void *obj) +{ + switch_timer_t timer = { 0 }; + pvt_t *pvt; + int samples = 240; + int ms = 30; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "timer thread started.\n"); + + if (switch_core_timer_init(&timer, "soft", ms, samples, NULL) != SWITCH_STATUS_SUCCESS) { + return NULL; + } + + t38_state_list.thread_running = 1; + + while(t38_state_list.thread_running) { + + switch_mutex_lock(t38_state_list.mutex); + + if (!t38_state_list.head) { + switch_mutex_unlock(t38_state_list.mutex); + goto end; + } + + for (pvt = t38_state_list.head; pvt; pvt = pvt->next) { + if (pvt->udptl_state) { + t38_terminal_send_timeout(pvt->t38_state, samples); + } + } + + switch_mutex_unlock(t38_state_list.mutex); + + switch_core_timer_next(&timer); + } + + end: + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "timer thread ended.\n"); + + t38_state_list.thread_running = 0; + switch_core_timer_destroy(&timer); + + return NULL; +} + +static void launch_timer_thread(void) +{ + + switch_threadattr_t *thd_attr = NULL; + + switch_threadattr_create(&thd_attr, 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); +} + + /***************************************************************************** LOGGING AND HELPER FUNCTIONS *****************************************************************************/ @@ -167,7 +311,6 @@ static void phase_e_handler(t30_state_t *s, void *user_data, int result) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "==============================================================================\n"); if (result == T30_ERR_OK) { - if (pvt->app_mode == FUNCTION_TX) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Fax successfully sent.\n"); } else if (pvt->app_mode == FUNCTION_RX) { @@ -195,8 +338,7 @@ static void phase_e_handler(t30_state_t *s, void *user_data, int result) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "remote vendor: %s\n", switch_str_nil(t30_get_rx_vendor(s))); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "remote model: %s\n", switch_str_nil(t30_get_rx_model(s))); - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, - "==============================================================================\n"); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "==============================================================================\n"); /* Set our channel variables @@ -259,14 +401,47 @@ static void phase_e_handler(t30_state_t *s, void *user_data, int result) */ } +static int t38_tx_packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count) +{ + switch_frame_t out_frame = { 0 }; + switch_core_session_t *session; + switch_channel_t *channel; + pvt_t *pvt; + uint8_t pkt[LOCAL_FAX_MAX_DATAGRAM]; + int x; + int r = 0; + + pvt = (pvt_t *) user_data; + session = pvt->session; + channel = switch_core_session_get_channel(session); + + /* we need to build a real packet here and make write_frame.packet and write_frame.packetlen point to it */ + out_frame.flags = SFF_UDPTL_PACKET | SFF_PROXY_PACKET; + out_frame.packet = pkt; + out_frame.packetlen = udptl_build_packet(pvt->udptl_state, pkt, buf, len); + + //switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "WRITE %d udptl bytes\n", out_frame.packetlen); + + for (x = 0; x < count; x++) { + if (switch_core_session_write_frame(session, &out_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { + r = -1; + break; + } + } + + return r; +} + static switch_status_t spanfax_init(pvt_t *pvt, transport_mode_t trans_mode) { switch_core_session_t *session; switch_channel_t *channel; fax_state_t *fax; + t38_terminal_state_t *t38; t30_state_t *t30; + session = (switch_core_session_t *) pvt->session; switch_assert(session); @@ -278,7 +453,8 @@ static switch_status_t spanfax_init(pvt_t *pvt, transport_mode_t trans_mode) case AUDIO_MODE: if (pvt->fax_state == NULL) { pvt->fax_state = (fax_state_t *) switch_core_session_alloc(pvt->session, sizeof(fax_state_t)); - } else { + } + if (pvt->fax_state == NULL) { return SWITCH_STATUS_FALSE; } @@ -300,54 +476,152 @@ static switch_status_t spanfax_init(pvt_t *pvt, transport_mode_t trans_mode) span_log_set_level(&fax->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); span_log_set_level(&t30->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); } - - t30_set_tx_ident(t30, pvt->ident); - t30_set_tx_page_header_info(t30, pvt->header); - - t30_set_phase_e_handler(t30, phase_e_handler, pvt); - - t30_set_supported_image_sizes(t30, - T30_SUPPORT_US_LETTER_LENGTH | T30_SUPPORT_US_LEGAL_LENGTH | T30_SUPPORT_UNLIMITED_LENGTH - | T30_SUPPORT_215MM_WIDTH | T30_SUPPORT_255MM_WIDTH | T30_SUPPORT_303MM_WIDTH); - t30_set_supported_resolutions(t30, - T30_SUPPORT_STANDARD_RESOLUTION | T30_SUPPORT_FINE_RESOLUTION | T30_SUPPORT_SUPERFINE_RESOLUTION - | T30_SUPPORT_R8_RESOLUTION | T30_SUPPORT_R16_RESOLUTION); - - - if (pvt->disable_v17) { - t30_set_supported_modems(t30, T30_SUPPORT_V29 | T30_SUPPORT_V27TER); - switch_channel_set_variable(channel, "fax_v17_disabled", "1"); - } else { - t30_set_supported_modems(t30, T30_SUPPORT_V29 | T30_SUPPORT_V27TER | T30_SUPPORT_V17); - switch_channel_set_variable(channel, "fax_v17_disabled", "0"); - } - - if (pvt->use_ecm) { - t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION); - t30_set_ecm_capability(t30, TRUE); - switch_channel_set_variable(channel, "fax_ecm_requested", "1"); - } else { - t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION); - switch_channel_set_variable(channel, "fax_ecm_requested", "0"); - } - - if (pvt->app_mode == FUNCTION_TX) { - t30_set_tx_file(t30, pvt->filename, pvt->tx_page_start, pvt->tx_page_end); - } else { - t30_set_rx_file(t30, pvt->filename, -1); - } - switch_channel_set_variable(channel, "fax_filename", pvt->filename); break; case T38_MODE: - /* - Here goes the T.38 SpanDSP initializing functions - T.38 will require a big effort as it needs a different approach - but the pieces are already in place - */ - default: - assert(0); /* Whaaat ? */ + if (pvt->t38_state == NULL) { + pvt->t38_state = (t38_terminal_state_t *) switch_core_session_alloc(pvt->session, sizeof(t38_terminal_state_t)); + } + if (pvt->t38_state == NULL) { + return SWITCH_STATUS_FALSE; + } + if (pvt->udptl_state == NULL) { + pvt->udptl_state = (udptl_state_t *) switch_core_session_alloc(pvt->session, sizeof(udptl_state_t)); + } + if (pvt->udptl_state == NULL) { + t38_terminal_free(pvt->t38_state); + pvt->t38_state = NULL; + return SWITCH_STATUS_FALSE; + } + + /* add to timer thread processing */ + add_pvt(pvt); + + t38 = pvt->t38_state; + t30 = t38_terminal_get_t30_state(t38); + + memset(t38, 0, sizeof(t38_terminal_state_t)); + + if (t38_terminal_init(t38, pvt->caller, t38_tx_packet_handler, pvt) == NULL) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot initialize my T.38 structs\n"); + return SWITCH_STATUS_FALSE; + } + + pvt->t38_core = t38_terminal_get_t38_core_state(pvt->t38_state); + + if (udptl_init(pvt->udptl_state, UDPTL_ERROR_CORRECTION_REDUNDANCY, 3, 3, + (udptl_rx_packet_handler_t *) t38_core_rx_ifp_packet, (void *) pvt->t38_core) == NULL) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot initialize my UDPTL structs\n"); + return SWITCH_STATUS_FALSE; + } + + span_log_set_message_handler(&t38->logging, spanfax_log_message); + span_log_set_message_handler(&t30->logging, spanfax_log_message); + + if (pvt->verbose) { + span_log_set_level(&t38->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + span_log_set_level(&t30->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + } break; + case T38_GATEWAY_MODE: + if (pvt->t38_gateway_state == NULL) { + pvt->t38_gateway_state = (t38_gateway_state_t *) switch_core_session_alloc(pvt->session, sizeof(t38_gateway_state_t)); + } + + if (pvt->udptl_state == NULL) { + pvt->udptl_state = (udptl_state_t *) switch_core_session_alloc(pvt->session, sizeof(udptl_state_t)); + } + + if (t38_gateway_init(pvt->t38_gateway_state, t38_tx_packet_handler, pvt) == NULL) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot initialize my T.38 structs\n"); + t38_gateway_free(pvt->t38_gateway_state); + pvt->t38_gateway_state = NULL; + + return SWITCH_STATUS_FALSE; + } + + pvt->t38_core = t38_gateway_get_t38_core_state(pvt->t38_gateway_state); + + if (udptl_init(pvt->udptl_state, UDPTL_ERROR_CORRECTION_REDUNDANCY, 3, 3, + (udptl_rx_packet_handler_t *) t38_core_rx_ifp_packet, (void *) pvt->t38_core) == NULL) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot initialize my UDPTL structs\n"); + t38_gateway_free(pvt->t38_gateway_state); + udptl_release(pvt->udptl_state); + pvt->udptl_state = NULL; + return SWITCH_STATUS_FALSE; + } + + t38_gateway_set_transmit_on_idle(pvt->t38_gateway_state, TRUE); + + if (switch_true(switch_channel_get_variable(channel, "fax_v17_disabled"))) { + t38_gateway_set_supported_modems(pvt->t38_gateway_state, T30_SUPPORT_V29 | T30_SUPPORT_V27TER); + } else { + t38_gateway_set_supported_modems(pvt->t38_gateway_state, T30_SUPPORT_V17 | T30_SUPPORT_V29 | T30_SUPPORT_V27TER); + } + + t38_gateway_set_ecm_capability(pvt->t38_gateway_state, pvt->use_ecm); + switch_channel_set_variable(channel, "fax_ecm_requested", pvt->use_ecm ? "true" : "false"); + + if (switch_true(switch_channel_get_variable(channel, "FAX_DISABLE_ECM"))) { + t38_gateway_set_ecm_capability(pvt->t38_gateway_state, FALSE); + } else { + t38_gateway_set_ecm_capability(pvt->t38_gateway_state, TRUE); + } + + + span_log_set_message_handler(&pvt->t38_gateway_state->logging, spanfax_log_message); + span_log_set_message_handler(&pvt->t38_core->logging, spanfax_log_message); + + if (pvt->verbose) { + span_log_set_level(&pvt->t38_gateway_state->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + span_log_set_level(&pvt->t38_core->logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + } + + t38_set_t38_version(pvt->t38_core, 0); + t38_gateway_set_ecm_capability(pvt->t38_gateway_state, 1); + + return SWITCH_STATUS_SUCCESS; + + default: + assert(0); /* What? */ + return SWITCH_STATUS_SUCCESS; } /* Switch trans mode */ + + /* All the things which are common to audio and T.38 FAX setup */ + t30_set_tx_ident(t30, pvt->ident); + t30_set_tx_page_header_info(t30, pvt->header); + + t30_set_phase_e_handler(t30, phase_e_handler, pvt); + + t30_set_supported_image_sizes(t30, + T30_SUPPORT_US_LETTER_LENGTH | T30_SUPPORT_US_LEGAL_LENGTH | T30_SUPPORT_UNLIMITED_LENGTH + | T30_SUPPORT_215MM_WIDTH | T30_SUPPORT_255MM_WIDTH | T30_SUPPORT_303MM_WIDTH); + t30_set_supported_resolutions(t30, + T30_SUPPORT_STANDARD_RESOLUTION | T30_SUPPORT_FINE_RESOLUTION | T30_SUPPORT_SUPERFINE_RESOLUTION + | T30_SUPPORT_R8_RESOLUTION | T30_SUPPORT_R16_RESOLUTION); + + if (pvt->disable_v17) { + t30_set_supported_modems(t30, T30_SUPPORT_V29 | T30_SUPPORT_V27TER); + switch_channel_set_variable(channel, "fax_v17_disabled", "1"); + } else { + t30_set_supported_modems(t30, T30_SUPPORT_V29 | T30_SUPPORT_V27TER | T30_SUPPORT_V17); + switch_channel_set_variable(channel, "fax_v17_disabled", "0"); + } + + if (pvt->use_ecm) { + t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION); + t30_set_ecm_capability(t30, TRUE); + switch_channel_set_variable(channel, "fax_ecm_requested", "1"); + } else { + t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION); + switch_channel_set_variable(channel, "fax_ecm_requested", "0"); + } + + if (pvt->app_mode == FUNCTION_TX) { + t30_set_tx_file(t30, pvt->filename, pvt->tx_page_start, pvt->tx_page_end); + } else { + t30_set_rx_file(t30, pvt->filename, -1); + } + switch_channel_set_variable(channel, "fax_filename", pvt->filename); return SWITCH_STATUS_SUCCESS; } @@ -357,6 +631,8 @@ static switch_status_t spanfax_destroy(pvt_t *pvt) int terminate; t30_state_t *t30; + if (!pvt) return SWITCH_STATUS_FALSE; + if (pvt->fax_state) { if (pvt->t38_state) { terminate = 0; @@ -373,6 +649,10 @@ static switch_status_t spanfax_destroy(pvt_t *pvt) } if (pvt->t38_state) { + + /* remove from timer thread processing */ + del_pvt(pvt); + if (pvt->t38_state) { terminate = 1; } else { @@ -380,6 +660,7 @@ static switch_status_t spanfax_destroy(pvt_t *pvt) } t30 = t38_terminal_get_t30_state(pvt->t38_state); + if (terminate && t30) { t30_terminate(t30); } @@ -387,28 +668,210 @@ static switch_status_t spanfax_destroy(pvt_t *pvt) t38_terminal_release(pvt->t38_state); } + if (pvt->t38_gateway_state) { + t38_gateway_release(pvt->t38_gateway_state); + } + + if (pvt->udptl_state) { + udptl_release(pvt->udptl_state); + } return SWITCH_STATUS_SUCCESS; } +static t38_mode_t configure_t38(pvt_t *pvt) +{ + switch_core_session_t *session = pvt->session; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_t38_options_t *t38_options = switch_channel_get_private(channel, "t38_options"); + int method = 2; + + if (!t38_options) { + pvt->t38_mode = T38_MODE_REFUSED; + return pvt->t38_mode; + } + + t38_set_t38_version(pvt->t38_core, t38_options->T38FaxVersion); + t38_set_max_buffer_size(pvt->t38_core, t38_options->T38FaxMaxBuffer); + t38_set_fastest_image_data_rate(pvt->t38_core, t38_options->T38MaxBitRate); + t38_set_fill_bit_removal(pvt->t38_core, t38_options->T38FaxFillBitRemoval); + t38_set_mmr_transcoding(pvt->t38_core, t38_options->T38FaxTranscodingMMR); + t38_set_jbig_transcoding(pvt->t38_core, t38_options->T38FaxTranscodingJBIG); + t38_set_max_datagram_size(pvt->t38_core, t38_options->T38FaxMaxDatagram); + + if (t38_options->T38FaxRateManagement) { + if (!strcasecmp(t38_options->T38FaxRateManagement, "transferredTCF")) { + method = 2; + } else { + method = 1; + } + } + + t38_set_data_rate_management_method(pvt->t38_core, method); + + + //t38_set_data_transport_protocol(pvt->t38_core, int data_transport_protocol); + //t38_set_redundancy_control(pvt->t38_core, int category, int setting); + + return pvt->t38_mode; +} + +static t38_mode_t negotiate_t38(pvt_t *pvt) +{ + switch_core_session_t *session = pvt->session; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_core_session_message_t msg = { 0 }; + switch_t38_options_t *t38_options = switch_channel_get_private(channel, "t38_options"); + int enabled = 0, insist = 0; + const char *v; + + pvt->t38_mode = T38_MODE_REFUSED; + + if (pvt->app_mode == FUNCTION_GW) { + enabled = 1; + } else if ((v = switch_channel_get_variable(channel, "fax_enable_t38"))) { + enabled = switch_true(v); + } else { + enabled = globals.enable_t38; + } + + if (!(enabled && t38_options)) { + /* if there is no t38_options the endpoint will refuse the transition */ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s NO T38 options detected.\n", switch_channel_get_name(channel)); + switch_channel_set_private(channel, "t38_options", NULL); + } else { + pvt->t38_mode = T38_MODE_NEGOTIATED; + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxVersion = %d\n", t38_options->T38FaxVersion); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38MaxBitRate = %d\n", t38_options->T38MaxBitRate); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxFillBitRemoval = %d\n", t38_options->T38FaxFillBitRemoval); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxTranscodingMMR = %d\n", t38_options->T38FaxTranscodingMMR); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxTranscodingJBIG = %d\n", t38_options->T38FaxTranscodingJBIG); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxRateManagement = '%s'\n", t38_options->T38FaxRateManagement); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxMaxBuffer = %d\n", t38_options->T38FaxMaxBuffer); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxMaxDatagram = %d\n", t38_options->T38FaxMaxDatagram); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38FaxUdpEC = '%s'\n", t38_options->T38FaxUdpEC); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "T38VendorInfo = '%s'\n", switch_str_nil(t38_options->T38VendorInfo)); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "ip = '%s'\n", t38_options->ip ? t38_options->ip : "Not specified"); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "port = %d\n", t38_options->port); + + /* Time to practice our negotiating skills, by editing the t38_options */ + + /* use default IP/PORT */ + t38_options->ip = NULL; + t38_options->port = 0; + + if (t38_options->T38FaxVersion > 3) { + t38_options->T38FaxVersion = 3; + } + t38_options->T38MaxBitRate = (pvt->disable_v17) ? 9600 : 14400; + t38_options->T38FaxFillBitRemoval = 1; + t38_options->T38FaxTranscodingMMR = 0; + t38_options->T38FaxTranscodingJBIG = 0; + t38_options->T38FaxRateManagement = "transferredTCF"; + t38_options->T38FaxMaxBuffer = 2000; + t38_options->T38FaxMaxDatagram = LOCAL_FAX_MAX_DATAGRAM; + if (strcasecmp(t38_options->T38FaxUdpEC, "t38UDPRedundancy") == 0 + || + strcasecmp(t38_options->T38FaxUdpEC, "t38UDPFEC") == 0) { + t38_options->T38FaxUdpEC = "t38UDPRedundancy"; + } else { + t38_options->T38FaxUdpEC = NULL; + } + t38_options->T38VendorInfo = "0 0 0"; + } + + if ((v = switch_channel_get_variable(channel, "fax_enable_t38_insist"))) { + insist = switch_true(v); + } else { + insist = globals.enable_t38_insist; + } + + /* This will send the options back in a response */ + msg.from = __FILE__; + msg.message_id = SWITCH_MESSAGE_INDICATE_T38_DESCRIPTION; + msg.numeric_arg = insist; + switch_core_session_receive_message(session, &msg); + + return pvt->t38_mode; +} + + + +static t38_mode_t request_t38(pvt_t *pvt) +{ + switch_core_session_t *session = pvt->session; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_core_session_message_t msg = { 0 }; + switch_t38_options_t *t38_options = NULL; + int enabled = 0, insist = 0; + const char *v; + + pvt->t38_mode = T38_MODE_UNKNOWN; + + if (pvt->app_mode == FUNCTION_GW) { + enabled = 1; + } else if ((v = switch_channel_get_variable(channel, "fax_enable_t38"))) { + enabled = switch_true(v); + } else { + enabled = 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; + } + } + + + if ((v = switch_channel_get_variable(channel, "fax_enable_t38_insist"))) { + insist = switch_true(v); + } else { + insist = globals.enable_t38_insist; + } + + if (enabled) { + t38_options = switch_core_session_alloc(session, sizeof(*t38_options)); + + t38_options->T38MaxBitRate = (pvt->disable_v17) ? 9600 : 14400; + t38_options->T38FaxVersion = 0; + t38_options->T38FaxFillBitRemoval = 1; + t38_options->T38FaxTranscodingMMR = 0; + t38_options->T38FaxTranscodingJBIG = 0; + t38_options->T38FaxRateManagement = "transferredTCF"; + t38_options->T38FaxMaxBuffer = 2000; + t38_options->T38FaxMaxDatagram = LOCAL_FAX_MAX_DATAGRAM; + t38_options->T38FaxUdpEC = "t38UDPRedundancy"; + t38_options->T38VendorInfo = "0 0 0"; + + /* use default IP/PORT */ + t38_options->ip = NULL; + t38_options->port = 0; + switch_channel_set_private(channel, "t38_options", t38_options); + pvt->t38_mode = T38_MODE_REQUESTED; + + /* This will send a request for t.38 mode */ + msg.from = __FILE__; + msg.message_id = SWITCH_MESSAGE_INDICATE_REQUEST_IMAGE_MEDIA; + msg.numeric_arg = insist; + switch_core_session_receive_message(session, &msg); + } + + return pvt->t38_mode; +} + /***************************************************************************** MAIN FAX PROCESSING *****************************************************************************/ -void process_fax(switch_core_session_t *session, const char *data, application_mode_t app_mode) +static pvt_t *pvt_init(switch_core_session_t *session, application_mode_t app_mode) { - pvt_t *pvt; - const char *tmp; + switch_channel_t *channel; + pvt_t *pvt = NULL; + const char *tmp; - switch_channel_t *channel; - switch_codec_t read_codec = { 0 }; - switch_codec_t write_codec = { 0 }; - switch_frame_t *read_frame = { 0 }; - switch_frame_t write_frame = { 0 }; - switch_codec_implementation_t read_impl = { 0 }; - int16_t *buf = NULL; - switch_core_session_get_read_impl(session, &read_impl); - - /* make sure we have a valid channel when starting the FAX application */ + /* Make sure we have a valid channel when starting the FAX application */ channel = switch_core_session_get_channel(session); switch_assert(channel != NULL); @@ -418,36 +881,25 @@ void process_fax(switch_core_session_t *session, const char *data, application_m /* Allocate our structs */ pvt = switch_core_session_alloc(session, sizeof(pvt_t)); + pvt->session = session; - counter_increment(); + pvt->app_mode = app_mode; - if (!pvt) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot allocate application private data\n"); - return; - } else { - memset(pvt, 0, sizeof(pvt_t)); - - pvt->session = session; - pvt->app_mode = app_mode; - - pvt->tx_page_start = -1; - pvt->tx_page_end = -1; - - if (pvt->app_mode == FUNCTION_TX) { - pvt->caller = 1; - } else if (pvt->app_mode == FUNCTION_RX) { - pvt->caller = 0; - } else { - assert(0); /* UH ? */ - } - } + pvt->tx_page_start = -1; + pvt->tx_page_end = -1; - buf = switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE); - if (!buf) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot allocate application buffer data\n"); - return; - } + switch(pvt->app_mode) { + + case FUNCTION_TX: + pvt->caller = 1; + break; + case FUNCTION_RX: + pvt->caller = 0; + break; + case FUNCTION_GW: + break; + } /* Retrieving our settings from the channel variables */ @@ -490,7 +942,6 @@ void process_fax(switch_core_session_t *session, const char *data, application_m } if (pvt->app_mode == FUNCTION_TX) { - if ((tmp = switch_channel_get_variable(channel, "fax_start_page"))) { pvt->tx_page_start = atoi(tmp); } @@ -510,7 +961,31 @@ void process_fax(switch_core_session_t *session, const char *data, application_m if ((pvt->tx_page_end < pvt->tx_page_start) && (pvt->tx_page_end != -1)) { pvt->tx_page_end = pvt->tx_page_start; } - } + } + + return pvt; +} + +void process_fax(switch_core_session_t *session, const char *data, application_mode_t app_mode) +{ + pvt_t *pvt; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_codec_t read_codec = { 0 }; + switch_codec_t write_codec = { 0 }; + switch_frame_t *read_frame = { 0 }; + switch_frame_t write_frame = { 0 }; + switch_codec_implementation_t read_impl = { 0 }; + int16_t *buf = NULL; + + switch_core_session_get_read_impl(session, &read_impl); + + counter_increment(); + + + pvt = pvt_init(session, app_mode); + + + buf = switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE); if (!zstr(data)) { pvt->filename = switch_core_session_strdup(session, data); @@ -581,7 +1056,6 @@ void process_fax(switch_core_session_t *session, const char *data, application_m * used internally by spandsp and FS will do the transcoding * from G.711 or any other original codec */ - if (switch_core_codec_init(&read_codec, "L16", NULL, @@ -617,6 +1091,11 @@ void process_fax(switch_core_session_t *session, const char *data, application_m switch_ivr_sleep(session, 250, SWITCH_TRUE, NULL); + + /* If you have the means, I highly recommend picking one up. ...*/ + request_t38(pvt); + + while (switch_channel_ready(channel)) { int tx = 0; switch_status_t status; @@ -630,7 +1109,7 @@ void process_fax(switch_core_session_t *session, const char *data, application_m - call t38_terminal_send_timeout(), sleep for a while The T.38 stuff can be placed here (and the audio stuff can be skipped) - */ + */ /* read new audio frame from the channel */ status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); @@ -640,9 +1119,67 @@ void process_fax(switch_core_session_t *session, const char *data, application_m goto done; } + switch (pvt->t38_mode) { + case T38_MODE_REQUESTED: + { + if (switch_channel_test_app_flag(channel, CF_APP_T38)) { + switch_core_session_message_t msg = { 0 }; + pvt->t38_mode = T38_MODE_NEGOTIATED; + spanfax_init(pvt, T38_MODE); + configure_t38(pvt); + + /* This will change the rtp stack to udptl mode */ + msg.from = __FILE__; + msg.message_id = SWITCH_MESSAGE_INDICATE_UDPTL_MODE; + switch_core_session_receive_message(session, &msg); + } + continue; + } + break; + case T38_MODE_UNKNOWN: + { + if (switch_channel_test_app_flag(channel, CF_APP_T38)) { + if (negotiate_t38(pvt) == T38_MODE_NEGOTIATED) { + /* is is safe to call this again, it was already called above in AUDIO_MODE */ + /* but this is the only way to set up the t38 stuff */ + spanfax_init(pvt, T38_MODE); + continue; + } + } + } + break; + case T38_MODE_NEGOTIATED: + { + /* do what we need to do when we are in t38 mode */ + if (switch_test_flag(read_frame, SFF_CNG)) { + /* dunno what to do, most likely you will not get too many of these since we turn off the timer in udptl mode */ + continue; + } + + if (switch_test_flag(read_frame, SFF_UDPTL_PACKET)) { + /* now we know we can cast frame->packet to a udptl structure */ + //switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "READ %d udptl bytes\n", read_frame->packetlen); + + udptl_rx_packet(pvt->udptl_state, read_frame->packet, read_frame->packetlen); + + + } + } + continue; + default: + break; + } + /* Skip CNG frames (auto-generated by FreeSWITCH, usually) */ - if (!switch_test_flag(read_frame, SFF_CNG)) { - /* pass the new incoming audio frame to the fax_rx function */ + if (switch_test_flag(read_frame, SFF_CNG)) { + /* We have no real signal data for the FAX software, but we have a space in time if we have a CNG indication. + Do a fill-in operation in the FAX machine, to keep things rolling along. */ + if (fax_rx_fillin(pvt->fax_state, read_frame->samples)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "fax_rx_fillin reported an error\n"); + goto done; + } + } else { + /* Pass the new incoming audio frame to the fax_rx function */ if (fax_rx(pvt->fax_state, (int16_t *) read_frame->data, read_frame->samples)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "fax_rx reported an error\n"); goto done; @@ -664,9 +1201,6 @@ void process_fax(switch_core_session_t *session, const char *data, application_m } if (switch_core_session_write_frame(session, &write_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { - /* something weird has happened */ - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, - "Cannot write frame [datalen: %d, samples: %d]\n", write_frame.datalen, write_frame.samples); goto done; } @@ -687,11 +1221,6 @@ void process_fax(switch_core_session_t *session, const char *data, application_m if (switch_core_codec_ready(&write_codec)) { switch_core_codec_destroy(&write_codec); } - - - - - } /* ************************************************************************** @@ -731,6 +1260,18 @@ void load_configuration(switch_bool_t reload) 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")) { @@ -853,19 +1394,6 @@ switch_status_t spandsp_inband_dtmf_session(switch_core_session_t *session) return SWITCH_STATUS_SUCCESS; } - - - - - - - - - - - - - /* ************************************************************************** FREESWITCH MODULE DEFINITIONS ************************************************************************* */ @@ -900,12 +1428,358 @@ SWITCH_STANDARD_APP(stop_dtmf_session_function) spandsp_stop_inband_dtmf_session(session); } +static const switch_state_handler_table_t t38_gateway_state_handlers; + +static switch_status_t t38_gateway_on_soft_execute(switch_core_session_t *session) +{ + switch_core_session_t *other_session; + + switch_channel_t *other_channel, *channel = switch_core_session_get_channel(session); + pvt_t *pvt; + const char *peer_uuid = switch_channel_get_variable(channel, "t38_peer"); + switch_core_session_message_t msg = { 0 }; + switch_status_t status; + switch_frame_t *read_frame = { 0 }; + + if (!(other_session = switch_core_session_locate(peer_uuid))) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s Cannot locate channel with uuid %s", + switch_channel_get_name(channel), peer_uuid); + goto end; + } + + other_channel = switch_core_session_get_channel(other_session); + + pvt = pvt_init(session, FUNCTION_GW); + request_t38(pvt); + + while (switch_channel_ready(channel) && switch_channel_up(other_channel) && !switch_channel_test_app_flag(channel, CF_APP_T38)) { + status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status) || pvt->done) { + /* Our duty is over */ + goto end_unlock; + } + + if (switch_test_flag(read_frame, SFF_CNG)) { + continue; + } + + if (switch_core_session_write_frame(other_session, read_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { + goto end_unlock; + } + } + + if (!(switch_channel_ready(channel) && switch_channel_up(other_channel))) { + goto end_unlock; + } + + if (!switch_channel_test_app_flag(channel, CF_APP_T38)) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s Could not negotiate T38\n", switch_channel_get_name(channel)); + goto end_unlock; + } + + if (pvt->t38_mode == T38_MODE_REQUESTED) { + configure_t38(pvt); + pvt->t38_mode = T38_MODE_NEGOTIATED; + } else { + if (negotiate_t38(pvt) != T38_MODE_NEGOTIATED) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s Could not negotiate T38\n", switch_channel_get_name(channel)); + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end_unlock; + } + } + + spanfax_init(pvt, T38_GATEWAY_MODE); + + /* This will change the rtp stack to udptl mode */ + msg.from = __FILE__; + msg.message_id = SWITCH_MESSAGE_INDICATE_UDPTL_MODE; + switch_core_session_receive_message(session, &msg); + + + /* wake up the audio side */ + switch_channel_set_private(channel, "_t38_pvt", pvt); + switch_channel_set_app_flag(other_channel, CF_APP_T38); + + + while (switch_channel_ready(channel) && switch_channel_up(other_channel)) { + + status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status) || pvt->done) { + /* Our duty is over */ + goto end_unlock; + } + + if (switch_test_flag(read_frame, SFF_CNG)) { + continue; + } + + if (switch_test_flag(read_frame, SFF_UDPTL_PACKET)) { + udptl_rx_packet(pvt->udptl_state, read_frame->packet, read_frame->packetlen); + } + } + + end_unlock: + + switch_channel_hangup(other_channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(other_session); + + end: + + switch_channel_clear_state_handler(channel, &t38_gateway_state_handlers); + switch_channel_set_variable(channel, "t38_peer", NULL); + + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t t38_gateway_on_consume_media(switch_core_session_t *session) +{ + switch_core_session_t *other_session; + switch_channel_t *other_channel, *channel = switch_core_session_get_channel(session); + const char *peer_uuid = switch_channel_get_variable(channel, "t38_peer"); + pvt_t *pvt = NULL; + switch_codec_t read_codec = { 0 }; + switch_codec_t write_codec = { 0 }; + switch_frame_t *read_frame = { 0 }; + switch_frame_t write_frame = { 0 }; + switch_codec_implementation_t read_impl = { 0 }; + int16_t *buf = NULL; + switch_status_t status; + switch_size_t tx; + + switch_core_session_get_read_impl(session, &read_impl); + + buf = switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE); + + if (!(other_session = switch_core_session_locate(peer_uuid))) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end; + } + + other_channel = switch_core_session_get_channel(other_session); + + while (switch_channel_ready(channel) && switch_channel_up(other_channel) && !switch_channel_test_app_flag(channel, CF_APP_T38)) { + status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status)) { + /* Our duty is over */ + goto end_unlock; + } + + if (switch_test_flag(read_frame, SFF_CNG)) { + continue; + } + + if (switch_core_session_write_frame(other_session, read_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { + goto end_unlock; + } + } + + if (!(switch_channel_ready(channel) && switch_channel_up(other_channel))) { + goto end_unlock; + } + + if (!switch_channel_test_app_flag(channel, CF_APP_T38)) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end_unlock; + } + + if (!(pvt = switch_channel_get_private(other_channel, "_t38_pvt"))) { + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end_unlock; + } + + if (switch_core_codec_init(&read_codec, + "L16", + NULL, + read_impl.samples_per_second, + read_impl.microseconds_per_packet / 1000, + 1, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Raw read codec activation Success L16 %u\n", + read_codec.implementation->microseconds_per_packet); + switch_core_session_set_read_codec(session, &read_codec); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Raw read codec activation Failed L16\n"); + goto end_unlock; + } + + if (switch_core_codec_init(&write_codec, + "L16", + NULL, + read_impl.samples_per_second, + read_impl.microseconds_per_packet / 1000, + 1, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Raw write codec activation Success L16\n"); + write_frame.codec = &write_codec; + write_frame.data = buf; + write_frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE; + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Raw write codec activation Failed L16\n"); + goto end_unlock; + } + + switch_ivr_sleep(session, 250, SWITCH_TRUE, NULL); + + while (switch_channel_ready(channel) && switch_channel_up(other_channel)) { + status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status) || pvt->done) { + /* Our duty is over */ + goto end_unlock; + } + + + /* Skip CNG frames (auto-generated by FreeSWITCH, usually) */ + if (!switch_test_flag(read_frame, SFF_CNG)) { + if (t38_gateway_rx(pvt->t38_gateway_state, (int16_t *) read_frame->data, read_frame->samples)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "fax_rx reported an error\n"); + goto end_unlock; + } + } + + if ((tx = t38_gateway_tx(pvt->t38_gateway_state, buf, write_codec.implementation->samples_per_packet)) < 0) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "fax_tx reported an error\n"); + goto end_unlock; + } + + if (!tx) { + /* switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No audio samples to send\n"); */ + continue; + } else { + /* Set our write_frame data */ + write_frame.datalen = tx * sizeof(int16_t); + write_frame.samples = tx; + } + + if (switch_core_session_write_frame(session, &write_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { + goto end_unlock; + } + } + + end_unlock: + + switch_channel_hangup(other_channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(other_session); + + switch_core_session_set_read_codec(session, NULL); + + if (switch_core_codec_ready(&read_codec)) { + switch_core_codec_destroy(&read_codec); + } + + if (switch_core_codec_ready(&write_codec)) { + switch_core_codec_destroy(&write_codec); + } + + + end: + + switch_channel_clear_state_handler(channel, &t38_gateway_state_handlers); + switch_channel_set_variable(channel, "t38_peer", NULL); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t t38_gateway_on_reset(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_channel_test_app_flag(channel, CF_APP_TAGGED)) { + switch_channel_clear_app_flag(channel, CF_APP_TAGGED); + switch_channel_set_state(channel, CS_CONSUME_MEDIA); + } else { + switch_channel_set_state(channel, CS_SOFT_EXECUTE); + } + + return SWITCH_STATUS_SUCCESS; +} + +static const switch_state_handler_table_t t38_gateway_state_handlers = { + /*.on_init */ NULL, + /*.on_routing */ NULL, + /*.on_execute */ NULL, + /*.on_hangup */ NULL, + /*.on_exchange_media */ NULL, + /*.on_soft_execute */ t38_gateway_on_soft_execute, + /*.on_consume_media */ t38_gateway_on_consume_media, + /*.on_hibernate */ NULL, + /*.on_reset */ t38_gateway_on_reset, + /*.on_park */ NULL, + /*.on_reporting */ NULL, + /*.on_destroy */ NULL, + SSH_FLAG_STICKY +}; + +static switch_bool_t t38_gateway_start(switch_core_session_t *session, const char *app, const char *data) +{ + switch_channel_t *other_channel = NULL, *channel = switch_core_session_get_channel(session); + switch_core_session_t *other_session = NULL; + int peer = app && !strcasecmp(app, "peer"); + + if (switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS) { + other_channel = switch_core_session_get_channel(other_session); + + switch_channel_set_variable(channel, "t38_peer", switch_core_session_get_uuid(other_session)); + switch_channel_set_variable(other_channel, "t38_peer", switch_core_session_get_uuid(session)); + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s starting gateway mode to %s\n", + switch_channel_get_name(peer ? channel : other_channel), + switch_channel_get_name(peer ? other_channel : channel)); + + + switch_channel_clear_state_handler(channel, NULL); + switch_channel_clear_state_handler(other_channel, NULL); + + switch_channel_add_state_handler(channel, &t38_gateway_state_handlers); + switch_channel_add_state_handler(other_channel, &t38_gateway_state_handlers); + + switch_channel_set_app_flag(peer ? channel : other_channel, CF_APP_TAGGED); + switch_channel_clear_app_flag(peer ? other_channel : channel, CF_APP_TAGGED); + + switch_channel_set_state(channel, CS_RESET); + switch_channel_set_state(other_channel, CS_RESET); + + switch_core_session_rwunlock(other_session); + + } + + return SWITCH_FALSE; +} + + +SWITCH_STANDARD_APP(t38_gateway_function) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (zstr(data) || strcasecmp(data, "self")) { + data = "peer"; + } + + switch_channel_set_variable(channel, "t38_leg", data); + + switch_ivr_tone_detect_session(session, "t38", "1100.0", "rw", 0, 1, data, NULL, t38_gateway_start); +} + + SWITCH_MODULE_LOAD_FUNCTION(mod_fax_init) { switch_application_interface_t *app_interface; *module_interface = switch_loadable_module_create_module_interface(pool, modname); + 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); + SWITCH_ADD_APP(app_interface, "rxfax", "FAX Receive Application", "FAX Receive Application", spanfax_rx_function, SPANFAX_RX_USAGE, SAF_SUPPORT_NOMEDIA); SWITCH_ADD_APP(app_interface, "txfax", "FAX Transmit Application", "FAX Transmit Application", spanfax_tx_function, SPANFAX_TX_USAGE, @@ -915,9 +1789,12 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_fax_init) SWITCH_ADD_APP(app_interface, "spandsp_start_dtmf", "Detect dtmf", "Detect inband dtmf on the session", dtmf_session_function, "", SAF_MEDIA_TAP); memset(&globals, 0, sizeof(globals)); + memset(&t38_state_list, 0, sizeof(t38_state_list)); switch_core_new_memory_pool(&globals.pool); switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); - + switch_mutex_init(&t38_state_list.mutex, SWITCH_MUTEX_NESTED, globals.pool); + + globals.enable_t38 = 1; globals.total_sessions = 0; globals.verbose = 1; globals.use_ecm = 1; diff --git a/src/mod/applications/mod_fax/udptl.c b/src/mod/applications/mod_fax/udptl.c new file mode 100644 index 0000000000..a2651513f0 --- /dev/null +++ b/src/mod/applications/mod_fax/udptl.c @@ -0,0 +1,563 @@ +//#define UDPTL_DEBUG +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2009, Steve Underwood + * + * 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. + * + * Contributor(s): + * + * Steve Underwood + * + * udptl.c -- UDPTL handling for T.38 + * + */ + +#include +#include +#include +#include +#include + +#include "udptl.h" + +#define FALSE 0 +#define TRUE (!FALSE) + +static int decode_length(const uint8_t *buf, int limit, int *len, int *pvalue) +{ + if (*len >= limit) + return -1; + if ((buf[*len] & 0x80) == 0) { + *pvalue = buf[(*len)++]; + return 0; + } + if ((buf[*len] & 0x40) == 0) { + if (*len >= limit - 1) + return -1; + *pvalue = (buf[(*len)++] & 0x3F) << 8; + *pvalue |= buf[(*len)++]; + return 0; + } + *pvalue = (buf[(*len)++] & 0x3F) << 14; + /* Indicate we have a fragment */ + return 1; +} + +/*- End of function --------------------------------------------------------*/ + +static int decode_open_type(const uint8_t *buf, int limit, int *len, const uint8_t ** p_object, int *p_num_octets) +{ + int octet_cnt; + int octet_idx; + int stat; + int i; + const uint8_t **pbuf; + + for (octet_idx = 0, *p_num_octets = 0;; octet_idx += octet_cnt) { + if ((stat = decode_length(buf, limit, len, &octet_cnt)) < 0) + return -1; + if (octet_cnt > 0) { + *p_num_octets += octet_cnt; + + pbuf = &p_object[octet_idx]; + i = 0; + /* Make sure the buffer contains at least the number of bits requested */ + if ((*len + octet_cnt) > limit) + return -1; + + *pbuf = &buf[*len]; + *len += octet_cnt; + } + if (stat == 0) + break; + } + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +static int encode_length(uint8_t *buf, int *len, int value) +{ + int multiplier; + + if (value < 0x80) { + /* 1 octet */ + buf[(*len)++] = value; + return value; + } + if (value < 0x4000) { + /* 2 octets */ + /* Set the first bit of the first octet */ + buf[(*len)++] = ((0x8000 | value) >> 8) & 0xFF; + buf[(*len)++] = value & 0xFF; + return value; + } + /* Fragmentation */ + multiplier = (value < 0x10000) ? (value >> 14) : 4; + /* Set the first 2 bits of the octet */ + buf[(*len)++] = 0xC0 | multiplier; + return multiplier << 14; +} + +/*- End of function --------------------------------------------------------*/ + +static int encode_open_type(uint8_t *buf, int *len, const uint8_t *data, int num_octets) +{ + int enclen; + int octet_idx; + uint8_t zero_byte; + + /* If open type is of zero length, add a single zero byte (10.1) */ + if (num_octets == 0) { + zero_byte = 0; + data = &zero_byte; + num_octets = 1; + } + /* Encode the open type */ + for (octet_idx = 0;; num_octets -= enclen, octet_idx += enclen) { + if ((enclen = encode_length(buf, len, num_octets)) < 0) + return -1; + if (enclen > 0) { + memcpy(&buf[*len], &data[octet_idx], enclen); + *len += enclen; + } + if (enclen >= num_octets) + break; + } + + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_rx_packet(udptl_state_t *s, const uint8_t buf[], int len) +{ + int stat; + int stat2; + int i; + int j; + int k; + int l; + int m; + int x; + int limit; + int which; + int ptr; + int count; + int total_count; + int seq_no; + const uint8_t *msg; + const uint8_t *data; + int msg_len; + int repaired[16]; + const uint8_t *bufs[16]; + int lengths[16]; + int span; + int entries; + + ptr = 0; + /* Decode seq_number */ + if (ptr + 2 > len) + return -1; + seq_no = (buf[0] << 8) | buf[1]; + ptr += 2; + /* Break out the primary packet */ + if ((stat = decode_open_type(buf, len, &ptr, &msg, &msg_len)) != 0) + return -1; + /* Decode error_recovery */ + if (ptr + 1 > len) + return -1; + /* Our buffers cannot tolerate overlength packets */ + if (msg_len > LOCAL_FAX_MAX_DATAGRAM) + return -1; + /* Update any missed slots in the buffer */ + for (i = s->rx_seq_no; seq_no > i; i++) { + x = i & UDPTL_BUF_MASK; + s->rx[x].buf_len = -1; + s->rx[x].fec_len[0] = 0; + s->rx[x].fec_span = 0; + s->rx[x].fec_entries = 0; + } + /* Save the new packet. Pure redundancy mode won't use this, but some systems will switch + into FEC mode after sending some redundant packets. */ + x = seq_no & UDPTL_BUF_MASK; + memcpy(s->rx[x].buf, msg, msg_len); + s->rx[x].buf_len = msg_len; + s->rx[x].fec_len[0] = 0; + s->rx[x].fec_span = 0; + s->rx[x].fec_entries = 0; + if ((buf[ptr++] & 0x80) == 0) { + /* Secondary packet mode for error recovery */ + /* We might have the packet we want, but we need to check through + the redundant stuff, and verify the integrity of the UDPTL. + This greatly reduces our chances of accepting garbage. */ + total_count = 0; + do { + if ((stat2 = decode_length(buf, len, &ptr, &count)) < 0) + return -1; + for (i = 0; i < count; i++) { + if ((stat = decode_open_type(buf, len, &ptr, &bufs[total_count + i], &lengths[total_count + i])) != 0) + return -1; + } + total_count += count; + } + while (stat2 > 0); + /* We should now be exactly at the end of the packet. If not, this is a fault. */ + if (ptr != len) + return -1; + if (seq_no > s->rx_seq_no) { + /* We received a later packet than we expected, so we need to check if we can fill in the gap from the + secondary packets. */ + /* Step through in reverse order, so we go oldest to newest */ + for (i = total_count; i > 0; i--) { + if (seq_no - i >= s->rx_seq_no) { + /* This one wasn't seen before */ + /* Decode the secondary packet */ +#if defined(UDPTL_DEBUG) + fprintf(stderr, "Secondary %d, len %d\n", seq_no - i, lengths[i - 1]); +#endif + /* Save the new packet. Redundancy mode won't use this, but some systems will switch into + FEC mode after sending some redundant packets, and this may then be important. */ + x = (seq_no - i) & UDPTL_BUF_MASK; + memcpy(s->rx[x].buf, bufs[i - 1], lengths[i - 1]); + s->rx[x].buf_len = lengths[i - 1]; + s->rx[x].fec_len[0] = 0; + s->rx[x].fec_span = 0; + s->rx[x].fec_entries = 0; + if (s->rx_packet_handler(s->user_data, bufs[i - 1], lengths[i - 1], seq_no - i) < 0) + fprintf(stderr, "Bad IFP\n"); + } + } + } + } else { + /* FEC mode for error recovery */ + + /* Decode the FEC packets */ + /* The span is defined as an unconstrained integer, but will never be more + than a small value. */ + if (ptr + 2 > len) + return -1; + if (buf[ptr++] != 1) + return -1; + span = buf[ptr++]; + + x = seq_no & UDPTL_BUF_MASK; + + s->rx[x].fec_span = span; + + memset(repaired, 0, sizeof(repaired)); + repaired[x] = TRUE; + + /* The number of entries is defined as a length, but will only ever be a small + value. Treat it as such. */ + if (ptr + 1 > len) + return -1; + entries = buf[ptr++]; + s->rx[x].fec_entries = entries; + + /* Decode the elements */ + for (i = 0; i < entries; i++) { + if ((stat = decode_open_type(buf, len, &ptr, &data, &s->rx[x].fec_len[i])) != 0) + return -1; + if (s->rx[x].fec_len[i] > LOCAL_FAX_MAX_DATAGRAM) + return -1; + + /* Save the new FEC data */ + memcpy(s->rx[x].fec[i], data, s->rx[x].fec_len[i]); +#if 0 + fprintf(stderr, "FEC: "); + for (j = 0; j < s->rx[x].fec_len[i]; j++) + fprintf(stderr, "%02X ", data[j]); + fprintf(stderr, "\n"); +#endif + } + /* We should now be exactly at the end of the packet. If not, this is a fault. */ + if (ptr != len) + return -1; + /* See if we can reconstruct anything which is missing */ + /* TODO: this does not comprehensively hunt back and repair everything that is possible */ + for (l = x; l != ((x - (16 - span * entries)) & UDPTL_BUF_MASK); l = (l - 1) & UDPTL_BUF_MASK) { + if (s->rx[l].fec_len[0] <= 0) + continue; + for (m = 0; m < s->rx[l].fec_entries; m++) { + limit = (l + m) & UDPTL_BUF_MASK; + for (which = -1, k = (limit - s->rx[l].fec_span * s->rx[l].fec_entries) & UDPTL_BUF_MASK; k != limit; + k = (k + s->rx[l].fec_entries) & UDPTL_BUF_MASK) { + if (s->rx[k].buf_len <= 0) + which = (which == -1) ? k : -2; + } + if (which >= 0) { + /* Repairable */ + for (j = 0; j < s->rx[l].fec_len[m]; j++) { + s->rx[which].buf[j] = s->rx[l].fec[m][j]; + for (k = (limit - s->rx[l].fec_span * s->rx[l].fec_entries) & UDPTL_BUF_MASK; k != limit; + k = (k + s->rx[l].fec_entries) & UDPTL_BUF_MASK) + s->rx[which].buf[j] ^= (s->rx[k].buf_len > j) ? s->rx[k].buf[j] : 0; + } + s->rx[which].buf_len = s->rx[l].fec_len[m]; + repaired[which] = TRUE; + } + } + } + /* Now play any new packets forwards in time */ + for (l = (x + 1) & UDPTL_BUF_MASK, j = seq_no - UDPTL_BUF_MASK; l != x; l = (l + 1) & UDPTL_BUF_MASK, j++) { + if (repaired[l]) { +#if defined(UDPTL_DEBUG) + fprintf(stderr, "Fixed packet %d, len %d\n", j, l); +#endif + if (s->rx_packet_handler(s->user_data, s->rx[l].buf, s->rx[l].buf_len, j) < 0) + fprintf(stderr, "Bad IFP\n"); + } + } + } + /* If packets are received out of sequence, we may have already processed this packet from the error + recovery information in a packet already received. */ + if (seq_no >= s->rx_seq_no) { + /* Decode the primary packet */ +#if defined(UDPTL_DEBUG) + fprintf(stderr, "Primary packet %d, len %d\n", seq_no, msg_len); +#endif + if (s->rx_packet_handler(s->user_data, msg, msg_len, seq_no) < 0) + fprintf(stderr, "Bad IFP\n"); + } + + s->rx_seq_no = (seq_no + 1) & 0xFFFF; + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_build_packet(udptl_state_t *s, uint8_t buf[], const uint8_t msg[], int msg_len) +{ + uint8_t fec[LOCAL_FAX_MAX_DATAGRAM]; + int i; + int j; + int seq; + int entry; + int entries; + int span; + int m; + int len; + int limit; + int high_tide; + + /* UDPTL cannot cope with zero length messages, and our buffering for redundancy limits their + maximum length. */ + if (msg_len < 1 || msg_len > LOCAL_FAX_MAX_DATAGRAM) + return -1; + seq = s->tx_seq_no & 0xFFFF; + + /* Map the sequence number to an entry in the circular buffer */ + entry = seq & UDPTL_BUF_MASK; + + /* We save the message in a circular buffer, for generating FEC or + redundancy sets later on. */ + s->tx[entry].buf_len = msg_len; + memcpy(s->tx[entry].buf, msg, msg_len); + + /* Build the UDPTL packet */ + + len = 0; + /* Encode the sequence number */ + buf[len++] = (seq >> 8) & 0xFF; + buf[len++] = seq & 0xFF; + + /* Encode the primary packet */ + if (encode_open_type(buf, &len, msg, msg_len) < 0) + return -1; + + /* Encode the appropriate type of error recovery information */ + switch (s->error_correction_scheme) { + case UDPTL_ERROR_CORRECTION_NONE: + /* Encode the error recovery type */ + buf[len++] = 0x00; + /* The number of entries will always be zero, so it is pointless allowing + for the fragmented case here. */ + if (encode_length(buf, &len, 0) < 0) + return -1; + break; + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + /* Encode the error recovery type */ + buf[len++] = 0x00; + if (s->tx_seq_no > s->error_correction_entries) + entries = s->error_correction_entries; + else + entries = s->tx_seq_no; + /* The number of entries will always be small, so it is pointless allowing + for the fragmented case here. */ + if (encode_length(buf, &len, entries) < 0) + return -1; + /* Encode the elements */ + for (i = 0; i < entries; i++) { + j = (entry - i - 1) & UDPTL_BUF_MASK; + if (encode_open_type(buf, &len, s->tx[j].buf, s->tx[j].buf_len) < 0) + return -1; + } + break; + case UDPTL_ERROR_CORRECTION_FEC: + span = s->error_correction_span; + entries = s->error_correction_entries; + if (seq < s->error_correction_span * s->error_correction_entries) { + /* In the initial stages, wind up the FEC smoothly */ + entries = seq / s->error_correction_span; + if (seq < s->error_correction_span) + span = 0; + } + /* Encode the error recovery type */ + buf[len++] = 0x80; + /* Span is defined as an inconstrained integer, which it dumb. It will only + ever be a small value. Treat it as such. */ + buf[len++] = 1; + buf[len++] = span; + /* The number of entries is defined as a length, but will only ever be a small + value. Treat it as such. */ + buf[len++] = entries; + for (m = 0; m < entries; m++) { + /* Make an XOR'ed entry the maximum length */ + limit = (entry + m) & UDPTL_BUF_MASK; + high_tide = 0; + for (i = (limit - span * entries) & UDPTL_BUF_MASK; i != limit; i = (i + entries) & UDPTL_BUF_MASK) { + if (high_tide < s->tx[i].buf_len) { + for (j = 0; j < high_tide; j++) + fec[j] ^= s->tx[i].buf[j]; + for (; j < s->tx[i].buf_len; j++) + fec[j] = s->tx[i].buf[j]; + high_tide = s->tx[i].buf_len; + } else { + for (j = 0; j < s->tx[i].buf_len; j++) + fec[j] ^= s->tx[i].buf[j]; + } + } + if (encode_open_type(buf, &len, fec, high_tide) < 0) + return -1; + } + break; + } + + if (s->verbose) + fprintf(stderr, "\n"); + s->tx_seq_no++; + return len; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_set_error_correction(udptl_state_t *s, int ec_scheme, int span, int entries) +{ + switch (ec_scheme) { + case UDPTL_ERROR_CORRECTION_FEC: + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + case UDPTL_ERROR_CORRECTION_NONE: + s->error_correction_scheme = ec_scheme; + break; + case -1: + /* Just don't change the scheme */ + break; + default: + return -1; + } + if (span >= 0) + s->error_correction_span = span; + if (entries >= 0) + s->error_correction_entries = entries; + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_get_error_correction(udptl_state_t *s, int *ec_scheme, int *span, int *entries) +{ + if (ec_scheme) + *ec_scheme = s->error_correction_scheme; + if (span) + *span = s->error_correction_span; + if (entries) + *entries = s->error_correction_entries; + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_set_local_max_datagram(udptl_state_t *s, int max_datagram) +{ + s->local_max_datagram_size = max_datagram; + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_get_local_max_datagram(udptl_state_t *s) +{ + return s->local_max_datagram_size; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_set_far_max_datagram(udptl_state_t *s, int max_datagram) +{ + s->far_max_datagram_size = max_datagram; + return 0; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_get_far_max_datagram(udptl_state_t *s) +{ + return s->far_max_datagram_size; +} + +/*- End of function --------------------------------------------------------*/ + +udptl_state_t *udptl_init(udptl_state_t *s, int ec_scheme, int span, int entries, udptl_rx_packet_handler_t rx_packet_handler, void *user_data) +{ + int i; + + if (rx_packet_handler == NULL) + return NULL; + + if (s == NULL) { + if ((s = (udptl_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + + s->error_correction_scheme = ec_scheme; + s->error_correction_span = span; + s->error_correction_entries = entries; + + s->far_max_datagram_size = LOCAL_FAX_MAX_DATAGRAM; + s->local_max_datagram_size = LOCAL_FAX_MAX_DATAGRAM; + + memset(&s->rx, 0, sizeof(s->rx)); + memset(&s->tx, 0, sizeof(s->tx)); + for (i = 0; i <= UDPTL_BUF_MASK; i++) { + s->rx[i].buf_len = -1; + s->tx[i].buf_len = -1; + } + + s->rx_packet_handler = rx_packet_handler; + s->user_data = user_data; + + return s; +} + +/*- End of function --------------------------------------------------------*/ + +int udptl_release(udptl_state_t *s) +{ + return 0; +} + +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ diff --git a/src/mod/applications/mod_fax/udptl.h b/src/mod/applications/mod_fax/udptl.h new file mode 100644 index 0000000000..b0eb1b2e5a --- /dev/null +++ b/src/mod/applications/mod_fax/udptl.h @@ -0,0 +1,153 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2009, Steve Underwood + * + * 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. + * + * Contributor(s): + * + * Steve Underwood + * + * udptl.h -- UDPTL handling for T.38 + * + */ + +#if !defined(_UDPTL_H_) +#define _UDPTL_H_ + +#define LOCAL_FAX_MAX_DATAGRAM 400 +#define LOCAL_FAX_MAX_FEC_PACKETS 5 + +#define UDPTL_BUF_MASK 15 + +typedef int (udptl_rx_packet_handler_t) (void *user_data, const uint8_t msg[], int len, int seq_no); + +typedef struct { + int buf_len; + uint8_t buf[LOCAL_FAX_MAX_DATAGRAM]; +} udptl_fec_tx_buffer_t; + +typedef struct { + int buf_len; + uint8_t buf[LOCAL_FAX_MAX_DATAGRAM]; + int fec_len[LOCAL_FAX_MAX_FEC_PACKETS]; + uint8_t fec[LOCAL_FAX_MAX_FEC_PACKETS][LOCAL_FAX_MAX_DATAGRAM]; + int fec_span; + int fec_entries; +} udptl_fec_rx_buffer_t; + +struct udptl_state_s { + udptl_rx_packet_handler_t *rx_packet_handler; + void *user_data; + + /*! This option indicates the error correction scheme used in transmitted UDPTL + packets. */ + int error_correction_scheme; + + /*! This option indicates the number of error correction entries transmitted in + UDPTL packets. */ + int error_correction_entries; + + /*! This option indicates the span of the error correction entries in transmitted + UDPTL packets (FEC only). */ + int error_correction_span; + + /*! This option indicates the maximum size of a datagram that can be accepted by + the remote device. */ + int far_max_datagram_size; + + /*! This option indicates the maximum size of a datagram that we are prepared to + accept. */ + int local_max_datagram_size; + + int verbose; + + int tx_seq_no; + int rx_seq_no; + int rx_expected_seq_no; + + udptl_fec_tx_buffer_t tx[UDPTL_BUF_MASK + 1]; + udptl_fec_rx_buffer_t rx[UDPTL_BUF_MASK + 1]; +}; + +enum { + UDPTL_ERROR_CORRECTION_NONE, + UDPTL_ERROR_CORRECTION_FEC, + UDPTL_ERROR_CORRECTION_REDUNDANCY +}; + +typedef struct udptl_state_s udptl_state_t; + +#if defined(__cplusplus) +extern "C" { +#endif + +/*! \brief Process an arriving UDPTL packet. + \param s The UDPTL context. + \param buf The UDPTL packet buffer. + \param len The length of the packet. + \return 0 for OK. */ + int udptl_rx_packet(udptl_state_t *s, const uint8_t buf[], int len); + +/*! \brief Construct a UDPTL packet, ready for transmission. + \param s The UDPTL context. + \param buf The UDPTL packet buffer. + \param msg The primary packet. + \param len The length of the primary packet. + \return The length of the constructed UDPTL packet. */ + int udptl_build_packet(udptl_state_t *s, uint8_t buf[], const uint8_t msg[], int msg_len); + +/*! \brief Change the error correction settings of a UDPTL context. + \param s The UDPTL context. + \param ec_scheme One of the optional error correction schemes. + \param span The packet span over which error correction should be applied. + \param entries The number of error correction entries to include in packets. + \return 0 for OK. */ + int udptl_set_error_correction(udptl_state_t *s, int ec_scheme, int span, int entries); + +/*! \brief Check the error correction settings of a UDPTL context. + \param s The UDPTL context. + \param ec_scheme One of the optional error correction schemes. + \param span The packet span over which error correction is being applied. + \param entries The number of error correction being included in packets. + \return 0 for OK. */ + int udptl_get_error_correction(udptl_state_t *s, int *ec_scheme, int *span, int *entries); + + int udptl_set_local_max_datagram(udptl_state_t *s, int max_datagram); + + int udptl_get_local_max_datagram(udptl_state_t *s); + + int udptl_set_far_max_datagram(udptl_state_t *s, int max_datagram); + + int udptl_get_far_max_datagram(udptl_state_t *s); + +/*! \brief Initialise a UDPTL context. + \param s The UDPTL context. + \param ec_scheme One of the optional error correction schemes. + \param span The packet span over which error correction should be applied. + \param entries The number of error correction entries to include in packets. + \param rx_packet_handler The callback function, used to report arriving IFP packets. + \param user_data An opaque pointer supplied to rx_packet_handler. + \return A pointer to the UDPTL context, or NULL if there was a problem. */ + udptl_state_t *udptl_init(udptl_state_t *s, int ec_scheme, int span, int entries, udptl_rx_packet_handler_t rx_packet_handler, void *user_data); + +/*! \brief Release a UDPTL context. + \param s The UDPTL context. + \return 0 for OK. */ + int udptl_release(udptl_state_t *s); + +#if defined(__cplusplus) +} +#endif +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/src/mod/applications/mod_t38gateway/Makefile.am b/src/mod/applications/mod_t38gateway/Makefile.am index 31367e30e5..b4980bf8a1 100644 --- a/src/mod/applications/mod_t38gateway/Makefile.am +++ b/src/mod/applications/mod_t38gateway/Makefile.am @@ -1,8 +1,17 @@ include $(top_srcdir)/build/modmake.rulesam MODNAME=mod_t38gateway +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 + +SPANDSP_DIR=$(switch_srcdir)/libs/spandsp +SPANDSP_BUILDDIR=$(switch_builddir)/libs/spandsp +SPANDSP_LA=$(SPANDSP_BUILDDIR)/src/libspandsp.la + mod_LTLIBRARIES = mod_t38gateway.la mod_t38gateway_la_SOURCES = mod_t38gateway.c udptl.c -mod_t38gateway_la_CFLAGS = $(AM_CFLAGS) -mod_t38gateway_la_LIBADD = $(switch_builddir)/libfreeswitch.la -mod_t38gateway_la_LDFLAGS = -avoid-version -module -no-undefined -shared +mod_t38gateway_la_CFLAGS = $(AM_CFLAGS) -I$(SPANDSP_DIR)/src -I$(TIFF_DIR)/libtiff -I$(SPANDSP_BUILDDIR)/src -I$(TIFF_BUILDDIR)/libtiff +mod_t38gateway_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(SPANDSP_LA) $(TIFF_LA) +mod_t38gateway_la_LDFLAGS = -avoid-version -module -no-undefined -shared -ljpeg + diff --git a/src/mod/applications/mod_t38gateway/mod_t38gateway.c b/src/mod/applications/mod_t38gateway/mod_t38gateway.c index ed866934f8..13a7cc837e 100644 --- a/src/mod/applications/mod_t38gateway/mod_t38gateway.c +++ b/src/mod/applications/mod_t38gateway/mod_t38gateway.c @@ -199,7 +199,7 @@ SWITCH_STANDARD_APP(t38gateway_start_function) t38gateway_info->t38_core = t38_gateway_get_t38_core_state(t38gateway_info->t38); t38gateway_info->udptl = udptl_init(NULL, UDPTL_ERROR_CORRECTION_REDUNDANCY, 3, 3, rx_packet_handler, (void *) t38gateway_info); - status = switch_core_media_bug_add(session, t38gateway_callback, t38gateway_info, 0, SMBF_READ_STREAM, &bug); + status = switch_core_media_bug_add(session, "T.38 gateway", NULL, t38gateway_callback, t38gateway_info, 0, SMBF_READ_STREAM, &bug); if (status != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Failure hooking to stream\n"); @@ -316,7 +316,7 @@ SWITCH_STANDARD_API(t38gateway_api_main) /* Add a media bug that allows me to intercept the * reading leg of the audio stream */ - status = switch_core_media_bug_add(t38gateway_session, t38gateway_callback, t38gateway_info, 0, SMBF_READ_STREAM, &bug); + status = switch_core_media_bug_add(t38gateway_session, "T.38 gateway", NULL, t38gateway_callback, t38gateway_info, 0, SMBF_READ_STREAM, &bug); /* If adding a media bug fails exit */ if (status != SWITCH_STATUS_SUCCESS) { diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c index d28f37e415..a1d367e5b9 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.c +++ b/src/mod/endpoints/mod_sofia/mod_sofia.c @@ -977,9 +977,9 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f if (tech_pvt->last_codec_ms && tech_pvt->last_codec_ms == codec_ms) { tech_pvt->mismatch_count++; } - + tech_pvt->last_codec_ms = codec_ms; - + if (tech_pvt->mismatch_count > MAX_MISMATCH_FRAMES) { if (switch_rtp_ready(tech_pvt->rtp_session) && codec_ms != tech_pvt->codec_ms) { const char *val; @@ -996,19 +996,23 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f } tech_pvt->read_frame.datalen = 0; - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, - "We were told to use ptime %d but what they meant to say was %d\n" - "This issue has so far been identified to happen on the following broken platforms/devices:\n" - "Linksys/Sipura aka Cisco\n" - "ShoreTel\n" - "Sonus/L3\n" - "We will try to fix it but some of the devices on this list are so broken who knows what will happen..\n", - (int) tech_pvt->codec_ms, (int) codec_ms); - switch_channel_set_variable_printf(channel, "sip_h_X-Broken-PTIME", "Adv=%d;Sent=%d", - (int) tech_pvt->codec_ms, (int) codec_ms); + if (codec_ms != tech_pvt->codec_ms) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, + "We were told to use ptime %d but what they meant to say was %d\n" + "This issue has so far been identified to happen on the following broken platforms/devices:\n" + "Linksys/Sipura aka Cisco\n" + "ShoreTel\n" + "Sonus/L3\n" + "We will try to fix it but some of the devices on this list are so broken,\n" + "who knows what will happen..\n", + (int) tech_pvt->codec_ms, (int) codec_ms); - tech_pvt->codec_ms = codec_ms; + switch_channel_set_variable_printf(channel, "sip_h_X-Broken-PTIME", "Adv=%d;Sent=%d", + (int) tech_pvt->codec_ms, (int) codec_ms); + + tech_pvt->codec_ms = codec_ms; + } if (sofia_glue_tech_set_codec(tech_pvt, 2) != SWITCH_STATUS_SUCCESS) { *frame = NULL; @@ -1049,6 +1053,7 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f tech_pvt->last_ts = 0; /* inform them of the codec they are actually sending */ + if (++tech_pvt->codec_reinvites > 2) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Ok, some devices *cough* X-lite *cough*\n" @@ -1065,7 +1070,10 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f } else { tech_pvt->mismatch_count = 0; } + tech_pvt->last_ts = tech_pvt->read_frame.timestamp; + + } else { tech_pvt->mismatch_count = 0; tech_pvt->last_ts = 0; @@ -1512,16 +1520,70 @@ static switch_status_t sofia_receive_message(switch_core_session_t *session, swi } break; + case SWITCH_MESSAGE_INDICATE_UDPTL_MODE: + { + switch_t38_options_t *t38_options = switch_channel_get_private(tech_pvt->channel, "t38_options"); + + if (!t38_options) { + nua_respond(tech_pvt->nh, SIP_488_NOT_ACCEPTABLE, TAG_END()); + goto end_lock; + } + + if (switch_rtp_ready(tech_pvt->rtp_session)) { + switch_rtp_udptl_mode(tech_pvt->rtp_session); + } + } + break; + case SWITCH_MESSAGE_INDICATE_T38_DESCRIPTION: + { + switch_t38_options_t *t38_options = switch_channel_get_private(tech_pvt->channel, "t38_options"); + + if (!t38_options) { + nua_respond(tech_pvt->nh, SIP_488_NOT_ACCEPTABLE, TAG_END()); + goto end_lock; + } + + if (switch_rtp_ready(tech_pvt->rtp_session)) { + switch_rtp_udptl_mode(tech_pvt->rtp_session); + } + + + sofia_glue_set_image_sdp(tech_pvt, t38_options, msg->numeric_arg); + + if (!sofia_test_flag(tech_pvt, TFLAG_BYE)) { + char *extra_headers = sofia_glue_get_extra_headers(channel, SOFIA_SIP_RESPONSE_HEADER_PREFIX); + if (sofia_use_soa(tech_pvt)) { + nua_respond(tech_pvt->nh, SIP_200_OK, + NUTAG_AUTOANSWER(0), + SIPTAG_CONTACT_STR(tech_pvt->reply_contact), + SIPTAG_CALL_INFO_STR(switch_channel_get_variable(tech_pvt->channel, SOFIA_SIP_HEADER_PREFIX "call_info")), + SOATAG_USER_SDP_STR(tech_pvt->local_sdp_str), + SOATAG_REUSE_REJECTED(1), SOATAG_ORDERED_USER(1), SOATAG_AUDIO_AUX("cn telephone-event"), NUTAG_INCLUDE_EXTRA_SDP(1), + TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), + TAG_END()); + } else { + nua_respond(tech_pvt->nh, SIP_200_OK, + NUTAG_AUTOANSWER(0), + NUTAG_MEDIA_ENABLE(0), + SIPTAG_CONTACT_STR(tech_pvt->reply_contact), + SIPTAG_CALL_INFO_STR(switch_channel_get_variable(tech_pvt->channel, SOFIA_SIP_HEADER_PREFIX "call_info")), + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR(tech_pvt->local_sdp_str), + TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), + TAG_END()); + } + switch_safe_free(extra_headers); + sofia_set_flag_locked(tech_pvt, TFLAG_ANS); + } + } + break; case SWITCH_MESSAGE_INDICATE_REQUEST_IMAGE_MEDIA: { - switch_t38_options_t *t38_options = (switch_t38_options_t *) msg->pointer_arg; - - sofia_glue_set_image_sdp(tech_pvt, t38_options); - - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Sending request for image media. %s\n", - switch_channel_get_name(channel), tech_pvt->local_sdp_str); + switch_t38_options_t *t38_options = switch_channel_get_private(tech_pvt->channel, "t38_options"); + sofia_glue_set_image_sdp(tech_pvt, t38_options, msg->numeric_arg); + switch_channel_set_flag(channel, CF_REQ_MEDIA); sofia_set_flag_locked(tech_pvt, TFLAG_SENT_UPDATE); sofia_glue_do_invite(session); diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h index b3f71b26c7..8d165f04ed 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.h +++ b/src/mod/endpoints/mod_sofia/mod_sofia.h @@ -930,7 +930,7 @@ void sofia_glue_toggle_hold(private_object_t *tech_pvt, int sendonly); const char *sofia_state_string(int state); switch_status_t sofia_glue_tech_set_codec(private_object_t *tech_pvt, int force); void sofia_wait_for_reply(struct private_object *tech_pvt, nua_event_t event, uint32_t timeout); -void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t *t38_options); +void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t *t38_options, int insist); /* * SLA (shared line appearance) entrypoints diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c index 19cccc6aed..6405a8704c 100644 --- a/src/mod/endpoints/mod_sofia/sofia.c +++ b/src/mod/endpoints/mod_sofia/sofia.c @@ -4578,6 +4578,11 @@ static void sofia_handle_sip_i_state(switch_core_session_t *session, int status, sdp_parser_free(parser); } } + + if (match && switch_channel_test_app_flag(tech_pvt->channel, CF_APP_T38)) { + goto done; + } + if (match) { if (sofia_glue_tech_choose_port(tech_pvt, 0) != SWITCH_STATUS_SUCCESS) { goto done; diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c index 7777ca0cb8..ce2483061a 100644 --- a/src/mod/endpoints/mod_sofia/sofia_glue.c +++ b/src/mod/endpoints/mod_sofia/sofia_glue.c @@ -37,7 +37,7 @@ #include -void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t *t38_options) +void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t *t38_options, int insist) { char buf[2048]; const char *ip = t38_options->ip; @@ -45,6 +45,9 @@ void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t * const char *family = "IP4"; const char *username = tech_pvt->profile->username; + + //sofia_clear_flag(tech_pvt, TFLAG_ENABLE_SOA); + if (!ip) { if (!(ip = tech_pvt->adv_sdp_audio_ip)) { ip = tech_pvt->proxy_sdp_audio_ip; @@ -76,15 +79,29 @@ void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t * } tech_pvt->session_id++; - + family = strchr(ip, ':') ? "IP6" : "IP4"; + + switch_snprintf(buf, sizeof(buf), "v=0\n" "o=%s %010u %010u IN %s %s\n" "s=%s\n" "c=IN %s %s\n" - "t=0 0\n" + "t=0 0\n", + username, + tech_pvt->owner_id, + tech_pvt->session_id, + family, + ip, + username, + family, + ip); + + + switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "m=image %d udptl t38\n" + "a=T38FaxVersion:%d\n" "a=T38MaxBitRate:%d\n" "%s" "%s" @@ -94,23 +111,32 @@ void sofia_glue_set_image_sdp(private_object_t *tech_pvt, switch_t38_options_t * "a=T38FaxMaxDatagram:%d\n" "a=T38FaxUdpEC:%s\n" "a=T38VendorInfo:%s\n", - username, - tech_pvt->owner_id, - tech_pvt->session_id, - family, - ip, - username, - family, - ip, port, + t38_options->T38FaxVersion, t38_options->T38MaxBitRate, t38_options->T38FaxFillBitRemoval ? "a=T38FaxFillBitRemoval\n" : "", t38_options->T38FaxTranscodingMMR ? "a=T38FaxTranscodingMMR\n" : "", t38_options->T38FaxTranscodingJBIG ? "a=T38FaxTranscodingJBIG\n" : "", t38_options->T38FaxRateManagement, - t38_options->T38FaxMaxBuffer, t38_options->T38FaxMaxDatagram, t38_options->T38FaxUdpEC, t38_options->T38VendorInfo); + t38_options->T38FaxMaxBuffer, + t38_options->T38FaxMaxDatagram, + t38_options->T38FaxUdpEC, + t38_options->T38VendorInfo); + + + + if (insist) { + switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "m=audio 0 RTP/AVP 19\n"); + } sofia_glue_tech_set_local_sdp(tech_pvt, buf, SWITCH_TRUE); + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "%s image media sdp:\n%s\n", + switch_channel_get_name(tech_pvt->channel), tech_pvt->local_sdp_str); + + } void sofia_glue_set_local_sdp(private_object_t *tech_pvt, const char *ip, uint32_t port, const char *sr, int force) @@ -1582,7 +1608,9 @@ switch_status_t sofia_glue_do_invite(switch_core_session_t *session) return status; } - sofia_glue_set_local_sdp(tech_pvt, NULL, 0, NULL, 0); + if (!switch_channel_get_private(tech_pvt->channel, "t38_options") || zstr(tech_pvt->local_sdp_str)) { + sofia_glue_set_local_sdp(tech_pvt, NULL, 0, NULL, 0); + } sofia_set_flag_locked(tech_pvt, TFLAG_READY); @@ -3417,10 +3445,24 @@ uint8_t sofia_glue_negotiate_sdp(switch_core_session_t *session, sdp_session_t * } if (got_udptl && m->m_type == sdp_media_image && m->m_port) { - switch_t38_options_t *t38_options = switch_core_session_alloc(tech_pvt->session, sizeof(switch_t38_options_t)); + switch_t38_options_t *t38_options = switch_channel_get_private(tech_pvt->channel, "t38_options"); + + if (!t38_options) { + t38_options = switch_core_session_alloc(tech_pvt->session, sizeof(switch_t38_options_t)); + } + + t38_options->port = m->m_port; + + if (m->m_connections) { + t38_options->ip = switch_core_session_strdup(tech_pvt->session, m->m_connections->c_address); + } else if (sdp && sdp->sdp_connection) { + t38_options->ip = switch_core_session_strdup(tech_pvt->session, sdp->sdp_connection->c_address); + } for (attr = m->m_attributes; attr; attr = attr->a_next) { - if (!strcasecmp(attr->a_name, "T38MaxBitRate") && attr->a_value) { + if (!strcasecmp(attr->a_name, "T38FaxVersion") && attr->a_value) { + t38_options->T38FaxVersion = (uint16_t) atoi(attr->a_value); + } else if (!strcasecmp(attr->a_name, "T38MaxBitRate") && attr->a_value) { t38_options->T38MaxBitRate = (uint32_t) atoi(attr->a_value); } else if (!strcasecmp(attr->a_name, "T38FaxFillBitRemoval")) { t38_options->T38FaxFillBitRemoval = SWITCH_TRUE; @@ -3440,13 +3482,13 @@ uint8_t sofia_glue_negotiate_sdp(switch_core_session_t *session, sdp_session_t * t38_options->T38VendorInfo = switch_core_session_strdup(tech_pvt->session, attr->a_value); } } - + switch_channel_set_variable(tech_pvt->channel, "has_t38", "true"); switch_channel_set_private(tech_pvt->channel, "t38_options", t38_options); - - //switch_channel_set_flag(tech_pvt->channel, CF_PROXY_MEDIA); - //switch_rtp_set_flag(tech_pvt->rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA); - + switch_channel_set_app_flag(tech_pvt->channel, CF_APP_T38); + /* do nothing here, mod_fax will trigger a response (if it's listening =/)*/ + match = 1; + goto done; } else if (m->m_type == sdp_media_audio && m->m_port && !got_audio) { sdp_rtpmap_t *map; for (attr = m->m_attributes; attr; attr = attr->a_next) { diff --git a/src/switch_core_session.c b/src/switch_core_session.c index 980280208d..842c3c9724 100644 --- a/src/switch_core_session.c +++ b/src/switch_core_session.c @@ -556,6 +556,7 @@ static const char *message_names[] = { "APPLICATION_EXEC", "APPLICATION_EXEC_COMPLETE", "PHONE_EVENT", + "T38_DESCRIPTION" "INVALID" }; diff --git a/src/switch_rtp.c b/src/switch_rtp.c index d3a06fa793..623b7e56b2 100644 --- a/src/switch_rtp.c +++ b/src/switch_rtp.c @@ -1025,6 +1025,33 @@ SWITCH_DECLARE(switch_port_t) switch_rtp_get_remote_port(switch_rtp_t *rtp_sessi } +SWITCH_DECLARE(switch_status_t) switch_rtp_udptl_mode(switch_rtp_t *rtp_session) +{ + READ_INC(rtp_session); + WRITE_INC(rtp_session); + + if (rtp_session->timer.timer_interface) { + switch_core_timer_destroy(&rtp_session->timer); + memset(&rtp_session->timer, 0, sizeof(rtp_session->timer)); + } + + switch_set_flag_locked(rtp_session, SWITCH_RTP_FLAG_UDPTL); + switch_set_flag_locked(rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA); + switch_socket_opt_set(rtp_session->sock_input, SWITCH_SO_NONBLOCK, FALSE); + + switch_clear_flag(rtp_session, SWITCH_RTP_FLAG_USE_TIMER); + switch_clear_flag(rtp_session, SWITCH_RTP_FLAG_NOBLOCK); + + WRITE_DEC(rtp_session); + READ_DEC(rtp_session); + + switch_set_flag_locked(rtp_session, SWITCH_RTP_FLAG_FLUSH); + + return SWITCH_STATUS_SUCCESS; + +} + + SWITCH_DECLARE(switch_status_t) switch_rtp_set_remote_address(switch_rtp_t *rtp_session, const char *host, switch_port_t port, switch_port_t remote_rtcp_port, switch_bool_t change_adv_addr, const char **err) { @@ -1227,7 +1254,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_change_interval(switch_rtp_t *rtp_ses rtp_session->timer_name, ms_per_packet / 1000, samples_per_interval, rtp_session->pool)) == SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, - "RE-Starting timer [%s] %d bytes per %dms\n", rtp_session->timer_name, samples_per_interval, ms_per_packet); + "RE-Starting timer [%s] %d bytes per %dms\n", rtp_session->timer_name, samples_per_interval, ms_per_packet / 1000); } else { memset(&rtp_session->timer, 0, sizeof(rtp_session->timer)); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, @@ -2451,9 +2478,14 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ } } - if (bytes && switch_test_flag(rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA)) { + if (bytes && (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA) || switch_test_flag(rtp_session, SWITCH_RTP_FLAG_UDPTL))) { /* Fast PASS! */ *flags |= SFF_PROXY_PACKET; + + if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_UDPTL)) { + *flags |= SFF_UDPTL_PACKET; + } + ret = (int) bytes; goto end; } @@ -3475,12 +3507,12 @@ SWITCH_DECLARE(int) switch_rtp_write_frame(switch_rtp_t *rtp_session, switch_fra return -1; } - if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA)) { + if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_PROXY_MEDIA) || switch_test_flag(rtp_session, SWITCH_RTP_FLAG_UDPTL)) { switch_size_t bytes; char bufa[30]; const char *tx_host; /* Fast PASS! */ - if (!switch_test_flag(frame, SFF_PROXY_PACKET)) { + if (!switch_test_flag(frame, SFF_PROXY_PACKET) && !switch_test_flag(frame, SFF_UDPTL_PACKET)) { return 0; } bytes = frame->packetlen;