diff --git a/src/mod/applications/mod_t38gateway/Makefile b/src/mod/applications/mod_t38gateway/Makefile new file mode 100644 index 0000000000..6ded01ebd6 --- /dev/null +++ b/src/mod/applications/mod_t38gateway/Makefile @@ -0,0 +1,6 @@ +BASE=../../../.. + +LOCAL_SOURCES=udptl.c +LOCAL_OBJS=udptl.o + +include $(BASE)/build/modmake.rules diff --git a/src/mod/applications/mod_t38gateway/mod_t38gateway.2008.vcproj b/src/mod/applications/mod_t38gateway/mod_t38gateway.2008.vcproj new file mode 100644 index 0000000000..b790b73e4e --- /dev/null +++ b/src/mod/applications/mod_t38gateway/mod_t38gateway.2008.vcproj @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mod/applications/mod_t38gateway/mod_t38gateway.c b/src/mod/applications/mod_t38gateway/mod_t38gateway.c new file mode 100644 index 0000000000..21f54ee9fb --- /dev/null +++ b/src/mod/applications/mod_t38gateway/mod_t38gateway.c @@ -0,0 +1,347 @@ +/* + * 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 + * + * mod_t38gateway.c -- A T.38 gateway + * + * This module uses the T.38 gateway engine of spandsp to create a T.38 gateway suitable for + * V.17, V.29, and V.27ter FAX operation. + * + */ + +#include + +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES +#include +#include + +#include "udptl.h" + +/*! Syntax of the API call. */ +#define T38GATEWAY_SYNTAX " " + +/*! Number of expected parameters in api call. */ +#define T38GATEWAY_PARAMS 2 + +/*! FreeSWITCH CUSTOM event types. */ +#define T38GATEWAY_EVENT_CNG "t38gateway::cng" +#define T38GATEWAY_EVENT_CED "t38gateway::ced" +#define T38GATEWAY_EVENT_PAGE "t38gateway::page" + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_t38gateway_shutdown); +SWITCH_STANDARD_API(t38gateway_api_main); + +SWITCH_MODULE_LOAD_FUNCTION(mod_t38gateway_load); +SWITCH_MODULE_DEFINITION(mod_t38gateway, mod_t38gateway_load, NULL, NULL); +SWITCH_STANDARD_APP(t38gateway_start_function); + +/*! Type that holds codec information. */ +typedef struct { + /*! The sampling rate of the audio stream. */ + int rate; + /*! The number of channels. */ + int channels; +} t38gateway_codec_info_t; + +/*! Type that holds session information pertinent to the t38gateway module. */ +typedef struct { + /*! Internal FreeSWITCH session. */ + switch_core_session_t *session; + /*! Codec information for the session. */ + t38gateway_codec_info_t t38gateway_codec; + t38_gateway_state_t *t38; + t38_core_state_t *t38_core; + udptl_state_t *udptl; +} t38gateway_session_info_t; + +static int tx_packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count) +{ + t38gateway_session_info_t *t; + + t = (t38gateway_session_info_t *) user_data; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int rx_packet_handler(void *user_data, const uint8_t *buf, int len, int seq_no) +{ + t38gateway_session_info_t *t; + + t = (t38gateway_session_info_t *) user_data; + t38_core_rx_ifp_packet(t->t38_core, buf, len, seq_no); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief The callback function that is called when new audio data becomes available + * + * @author Steve Underwood + * @param bug A reference to the media bug. + * @param user_data The session information for this call. + * @param type The switch callback type. + * @return The success or failure of the function. + */ +static switch_bool_t t38gateway_callback(switch_media_bug_t * bug, void *user_data, switch_abc_type_t type) +{ + t38gateway_session_info_t *t38gateway_info; + switch_codec_t *read_codec; + switch_frame_t *frame; + int16_t *amp; + + t38gateway_info = (t38gateway_session_info_t *) user_data; + if (t38gateway_info == NULL) { + return SWITCH_FALSE; + } + + switch (type) { + case SWITCH_ABC_TYPE_INIT: + read_codec = switch_core_session_get_read_codec(t38gateway_info->session); + t38gateway_info->t38gateway_codec.rate = read_codec->implementation->samples_per_second; + t38gateway_info->t38gateway_codec.channels = read_codec->implementation->number_of_channels; + break; + case SWITCH_ABC_TYPE_READ_PING: + case SWITCH_ABC_TYPE_CLOSE: + break; + case SWITCH_ABC_TYPE_READ: + frame = switch_core_media_bug_get_read_replace_frame(bug); + amp = (int16_t *) frame->data; + t38_gateway_rx(t38gateway_info->t38, amp, frame->samples); + break; + case SWITCH_ABC_TYPE_WRITE: + case SWITCH_ABC_TYPE_READ_REPLACE: + case SWITCH_ABC_TYPE_WRITE_REPLACE: + break; + } + + return SWITCH_TRUE; +} + +/*! \brief FreeSWITCH module loading function + * + * @author Steve Underwood + * @return Load success or failure. + */ +SWITCH_MODULE_LOAD_FUNCTION(mod_t38gateway_load) +{ + switch_application_interface_t *app_interface; + switch_api_interface_t *api_interface; + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "T.38 gateway enabled\n"); + + SWITCH_ADD_APP(app_interface, "t38gateway", "T.38 gateway", "T.38 gateway", t38gateway_start_function, "[start] [stop]", SAF_NONE); + + SWITCH_ADD_API(api_interface, "t38gateway", "T.38 gateway", t38gateway_api_main, T38GATEWAY_SYNTAX); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/*! \brief FreeSWITCH application handler function. + * This handles calls made from applications such as LUA and the dialplan + * + * @author Steve Underwood + * @return Success or failure of the function. + */ +SWITCH_STANDARD_APP(t38gateway_start_function) +{ + switch_media_bug_t *bug; + switch_status_t status; + switch_channel_t *channel; + t38gateway_session_info_t *t38gateway_info; + + if (session == NULL) + return; + + channel = switch_core_session_get_channel(session); + + /* Is this channel already set? */ + bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_t38gateway_"); + /* If yes */ + if (bug != NULL) { + /* If we have a stop remove audio bug */ + if (strcasecmp(data, "stop") == 0) { + switch_channel_set_private(channel, "_t38gateway_", NULL); + switch_core_media_bug_remove(session, &bug); + return; + } + + /* We have already started */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot run 2 at once on the same channel!\n"); + + return; + } + + t38gateway_info = (t38gateway_session_info_t *) switch_core_session_alloc(session, sizeof(t38gateway_session_info_t)); + + t38gateway_info->session = session; + t38gateway_info->t38 = t38_gateway_init(NULL, tx_packet_handler, (void *) t38gateway_info); + 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); + + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failure hooking to stream\n"); + return; + } + + switch_channel_set_private(channel, "_t38gateway_", bug); +} + +/*! \brief Called when the module shuts down + * + * @author Steve Underwood + * @return The success or failure of the function. + */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_t38gateway_shutdown) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "T.38 gateway disabled\n"); + + return SWITCH_STATUS_SUCCESS; +} + +/*! \brief FreeSWITCH API handler function. + * This function handles API calls such as the ones from mod_event_socket and in some cases + * scripts such as LUA scripts. + * + * @author Steve Underwood + * @return The success or failure of the function. + */ +SWITCH_STANDARD_API(t38gateway_api_main) +{ + switch_core_session_t *t38gateway_session; + switch_media_bug_t *bug; + t38gateway_session_info_t *t38gateway_info; + switch_channel_t *channel; + switch_status_t status; + int argc; + char *argv[T38GATEWAY_PARAMS]; + char *ccmd; + char *uuid; + char *command; + + /* No command? Display usage */ + if (cmd == NULL) { + stream->write_function(stream, "-USAGE: %s\n", T38GATEWAY_SYNTAX); + return SWITCH_STATUS_SUCCESS; + } + + /* Duplicated contents of original string */ + ccmd = strdup(cmd); + /* Separate the arguments */ + argc = switch_separate_string(ccmd, ' ', argv, T38GATEWAY_PARAMS); + + /* If we don't have the expected number of parameters + * display usage */ + if (argc != T38GATEWAY_PARAMS) { + stream->write_function(stream, "-USAGE: %s\n", T38GATEWAY_SYNTAX); + switch_safe_free(ccmd); + return SWITCH_STATUS_SUCCESS; + } + + uuid = argv[0]; + command = argv[1]; + + /* using uuid locate a reference to the FreeSWITCH session */ + t38gateway_session = switch_core_session_locate(uuid); + + /* If the session was not found exit */ + if (t38gateway_session == NULL) { + switch_safe_free(ccmd); + stream->write_function(stream, "-USAGE: %s\n", T38GATEWAY_SYNTAX); + return SWITCH_STATUS_FALSE; + } + + /* Get current channel of the session to tag the session + * This indicates that our module is present */ + channel = switch_core_session_get_channel(t38gateway_session); + + /* Is this channel already set? */ + bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_t38gateway_"); + /* If yes */ + if (bug != NULL) { + /* If we have a stop remove audio bug */ + if (strcasecmp(command, "stop") == 0) { + switch_channel_set_private(channel, "_t38gateway_", NULL); + switch_core_media_bug_remove(t38gateway_session, &bug); + switch_safe_free(ccmd); + stream->write_function(stream, "+OK\n"); + return SWITCH_STATUS_SUCCESS; + } + + /* We have already started */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot run 2 at once on the same channel!\n"); + + switch_safe_free(ccmd); + return SWITCH_STATUS_FALSE; + } + + /* If we don't see the expected start exit */ + if (strcasecmp(command, "start") != 0) { + switch_safe_free(ccmd); + stream->write_function(stream, "-USAGE: %s\n", T38GATEWAY_SYNTAX); + return SWITCH_STATUS_FALSE; + } + + /* Allocate memory attached to this FreeSWITCH session for + * use in the callback routine and to store state information */ + t38gateway_info = (t38gateway_session_info_t *) switch_core_session_alloc(t38gateway_session, sizeof(t38gateway_session_info_t)); + + /* Set initial values and states */ + t38gateway_info->session = t38gateway_session; + t38gateway_info->t38 = t38_gateway_init(NULL, tx_packet_handler, (void *) t38gateway_info); + 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); + + /* 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); + + /* If adding a media bug fails exit */ + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failure hooking to stream\n"); + + switch_safe_free(ccmd); + return SWITCH_STATUS_FALSE; + } + + /* Set the t38gateway tag to detect an existing t38gateway media bug */ + switch_channel_set_private(channel, "_t38gateway_", bug); + + /* Everything went according to plan! Notify the user */ + stream->write_function(stream, "+OK\n"); + + switch_safe_free(ccmd); + return SWITCH_STATUS_SUCCESS; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: + */ diff --git a/src/mod/applications/mod_t38gateway/udptl.c b/src/mod/applications/mod_t38gateway/udptl.c new file mode 100644 index 0000000000..b5b89cdb2a --- /dev/null +++ b/src/mod/applications/mod_t38gateway/udptl.c @@ -0,0 +1,596 @@ +//#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_t38gateway/udptl.h b/src/mod/applications/mod_t38gateway/udptl.h new file mode 100644 index 0000000000..dd92f49840 --- /dev/null +++ b/src/mod/applications/mod_t38gateway/udptl.h @@ -0,0 +1,170 @@ +/* + * 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 ------------------------------------------------------------*/