diff -r -u asterisk-1.6.2.6/apps/app_dial.c asterisk-1.6.2.6-t38gw/apps/app_dial.c --- asterisk-1.6.2.6/apps/app_dial.c 2010-02-02 21:35:44.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/apps/app_dial.c 2010-03-15 15:39:46.000000000 +0100 @@ -93,6 +93,10 @@ Play an announcement to the called party, where x is the prompt to be played + @@ -472,6 +476,7 @@ OPT_GO_ON = (1 << 5), OPT_CALLEE_HANGUP = (1 << 6), OPT_CALLER_HANGUP = (1 << 7), + OPT_BRIDGE_T38 = (1 << 8), OPT_DURATION_LIMIT = (1 << 9), OPT_MUSICBACK = (1 << 10), OPT_CALLEE_MACRO = (1 << 11), @@ -521,6 +526,7 @@ AST_APP_OPTIONS(dial_exec_options, BEGIN_OPTIONS AST_APP_OPTION_ARG('A', OPT_ANNOUNCE, OPT_ARG_ANNOUNCE), + AST_APP_OPTION('b', OPT_BRIDGE_T38), AST_APP_OPTION('C', OPT_RESETCDR), AST_APP_OPTION('c', OPT_CANCEL_ELSEWHERE), AST_APP_OPTION('d', OPT_DTMF_EXIT), @@ -2255,7 +2261,17 @@ ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0); } - res = ast_bridge_call(chan, peer, &config); + + /* Unless we want the T.38 fax bridge, use standard bridging function */ + if (ast_test_flag64(&opts, OPT_BRIDGE_T38) && (ast_bridge_t38 == NULL)) { + ast_log(LOG_WARNING, "You need to load app_fax.so first to use the fax gateway option. Disabling T.38 gateway.\n"); + } + + if (!ast_test_flag64(&opts, OPT_BRIDGE_T38) || (ast_bridge_t38 == NULL)) { + res = ast_bridge_call(chan, peer, &config); + } else { + res = ast_bridge_t38(chan, peer); + } } strcpy(peer->context, chan->context); diff -r -u asterisk-1.6.2.6/apps/app_fax.c asterisk-1.6.2.6-t38gw/apps/app_fax.c --- asterisk-1.6.2.6/apps/app_fax.c 2010-02-09 19:09:34.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/apps/app_fax.c 2010-03-15 15:58:51.000000000 +0100 @@ -7,6 +7,22 @@ * * Code based on original implementation by Steve Underwood * + * Initial T.38-gateway code + * 2008, Daniel Ferenci + * Created by Nethemba s.r.o. http://www.nethemba.com + * Sponsored by IPEX a.s. http://www.ipex.cz + * + * T.38-gateway integration into asterisk app_fax and rework + * 2008, Gregory Hinton Nietsky + * dns Telecom http://www.dnstelecom.co.za + * + * Modified to make T.38-gateway compatible with Asterisk 1.6.2 + * 2010, Anton Verevkin + * ViaNetTV http://www.vianettv.com + * + * Modified to make T.38-gateway work + * 2010, Klaus Darilion, IPCom GmbH, www.ipcom.at + * * This program is free software, distributed under the terms of * the GNU General Public License * @@ -35,6 +51,7 @@ #include #include "asterisk/lock.h" +#include "asterisk/features.h" #include "asterisk/file.h" #include "asterisk/logger.h" #include "asterisk/channel.h" @@ -138,13 +155,37 @@ - + + + T.38 Gateway Handling + + + + Dial string in format (technology/[device:]number1) + + + Specifies the number of seconds we attempt to dial the specified devices + If not specified, this defaults to 35 seconds. + + + + Dials specified channel and bridges voice and fax. If both channels use voice or + T.38, then FaxGateway just acts as a thumb bridge. I one of the channels uses T.38 and + the other channel uses voice, then FaxGateway activates the T.38-Gateway and translates + between T.38 and voice. + Use instead of Dial application. + Note: This application is not yet complete as it does not forward indications (e.g. Rigning) + as long as the outgoing channel is not answered. + + ***/ static char *app_sndfax_name = "SendFAX"; static char *app_rcvfax_name = "ReceiveFAX"; +static char *app_t38gateway_name = "FaxGateway"; #define MAX_SAMPLES 240 +#define ENGAGE_UDPTL_NAT_RETRY 3 /* Watchdog. I have seen situations when remote fax disconnects (because of poor line quality) while SpanDSP continues staying in T30_STATE_IV_CTC state forever. @@ -157,6 +198,7 @@ typedef struct { struct ast_channel *chan; + struct ast_channel *peer; enum ast_t38_state t38state; /* T38 state of the channel */ int direction; /* Fax direction: 0 - receiving, 1 - sending */ int caller_mode; @@ -191,6 +233,8 @@ AST_FRAME_SET_BUFFER(&outf, buf, 0, len); + ast_debug(9, "sending T.38 frame (%d/%d) with len=%d on channel %s\n", outf.frametype, outf.subclass, outf.datalen, chan->name); + if (ast_write(chan, &outf) < 0) { ast_log(LOG_WARNING, "Unable to write frame to channel; %s\n", strerror(errno)); return -1; @@ -825,6 +869,271 @@ return res; } +static void ast_t38_gateway(fax_session *s) +{ + int timeout, i; +// int samples, len; + struct ast_channel *active = NULL; + struct ast_channel *inactive = NULL; + enum ast_t38_state activestate, inactivestate; + struct ast_frame *f; + t38_core_state_t *t38dsp; + struct ast_channel *channels[2]; +// int16_t buffer[T38_MAX_HDLC_LEN]; +// int res, original_read_fmt, original_write_fmt; + t38_gateway_state_t t38_state; + t38_stats_t t38_stats; + +// struct ast_frame outf = { +// .frametype = AST_FRAME_VOICE, +// .subclass = AST_FORMAT_SLINEAR, +// .src = "T38Gateway", +// }; + + ast_debug(1, "T.38-Gateway starting for chan %s and peer %s\n", s->chan->name, s->peer->name); + + /* channels[0] is the T.38 channel, channels[1] is the voice channel */ + if (ast_channel_get_t38_state(s->chan) == T38_STATE_NEGOTIATED) { + channels[0]=s->chan; + channels[1]=s->peer; + } else { + channels[0]=s->peer; + channels[1]=s->chan; + } + +#if SPANDSP_RELEASE_DATE >= 20081012 + /* for spandsp shaphots 0.0.6 and higher */ + t38dsp=&t38_state.t38x.t38; +#else + /* for spandsp release 0.0.5 */ + t38dsp=&t38_state.t38; +#endif + +// /* shouldn't be necessary as formats are already set in t38gateway_exec */ +// original_read_fmt = s->chan->readformat; +// if (original_read_fmt != AST_FORMAT_SLINEAR) { +// res = ast_set_read_format(s->chan, AST_FORMAT_SLINEAR); +// if (res < 0) { +// ast_log(LOG_ERROR, "Unable to set to linear read mode, giving up\n"); +// s->finished=-1; +// return; +// } +// } +// +// original_write_fmt = s->chan->writeformat; +// if (original_write_fmt != AST_FORMAT_SLINEAR) { +// res = ast_set_write_format(s->chan, AST_FORMAT_SLINEAR); +// if (res < 0) { +// ast_log(LOG_ERROR, "Unable to set to linear write mode, giving up\n"); +// s->finished=-1; +// return; +// } +// } + + if (t38_gateway_init(&t38_state, t38_tx_packet_handler, channels[0])) { + + t38_gateway_set_transmit_on_idle(&t38_state, TRUE); + + span_log_set_message_handler(&t38_state.logging, span_message); + span_log_set_message_handler(&t38dsp->logging, span_message); + span_log_set_level(&t38_state.logging, SPAN_LOG_WARNING + option_debug); + span_log_set_level(&t38dsp->logging, SPAN_LOG_WARNING + option_debug); + + t38_set_t38_version(t38dsp, 0); + t38_gateway_set_ecm_capability(&t38_state, TRUE); + t38_set_sequence_number_handling(t38dsp, TRUE); + t38_gateway_set_supported_modems(&t38_state, T30_SUPPORT_V27TER | T30_SUPPORT_V17 | T30_SUPPORT_V29); + + /* engage udptl nat on other side of T38 line + * (Asterisk changes media ports thus we send a few packets to reinitialize + * pinholes in NATs and FWs + */ + for (i=0; i= 20091228 + t38_core_send_indicator(&t38_state.t38x.t38, T38_IND_NO_SIGNAL); +#else +#if SPANDSP_RELEASE_DATE >= 20081012 + t38_core_send_indicator(&t38_state.t38x.t38, T38_IND_NO_SIGNAL, t38_state.t38x.t38.indicator_tx_count); +#else + t38_core_send_indicator(&t38_state.t38, T38_IND_NO_SIGNAL, t38_state.t38.indicator_tx_count); +#endif +#endif + } + + for(;;) { + timeout=20; + active = ast_waitfor_n(channels, 2, &timeout); + if (active) { + inactive = (active == channels[0]) ? channels[1] : channels[0]; + activestate = ast_channel_get_t38_state(active); + inactivestate = ast_channel_get_t38_state(inactive); + + if ((activestate == inactivestate) + || ((activestate != T38_STATE_NEGOTIATED) && (inactivestate != T38_STATE_NEGOTIATED))) + { + ast_debug(1, "Gatewaying finished for bridge state0 %d state1 %d\n", activestate, inactivestate); + break; + } + + f = ast_read(active); + if (f) { + if (active == channels[0]) { + /* packet received from T38 channel */ + if (f->frametype == AST_FRAME_MODEM && f->subclass == AST_MODEM_T38) { + if (t38_core_rx_ifp_packet(t38dsp, f->data.ptr, f->datalen, f->seqno)) { + ast_log(LOG_ERROR, "Error processing T.38 packet, terminating T.38-Gateway bridge.\n"); + s->finished=-1; + break; + } + } + } else { + /* packet received from non-T38 channel */ + /* we should not be T.38 if we are something went wrong with T.38 negotiation*/ + if (f->frametype == AST_FRAME_MODEM && f->subclass == AST_MODEM_T38) { + ast_log(LOG_ERROR, "Error: voice channel mutated into T.38 channel, terminating T.38-Gateway bridge.\n"); + s->finished=-1; + break; + } + t38_gateway_rx(&t38_state, f->data.ptr, f->samples); + /* Every time we receive a voice frame, we also generate a voice frame. + * Thus, we rely on the voice-peer to make proper RTP timing + */ +// /* Note: this might cause problems if the received frame contains more samples +// * than T38_MAX_HDLC_LEN. +// */ +// samples = (f->samples <= T38_MAX_HDLC_LEN) ? f->samples : T38_MAX_HDLC_LEN; +// if ((len = t38_gateway_tx(&t38_state, buffer, samples))) { +// AST_FRAME_SET_BUFFER(&outf, buffer, 0, len*sizeof(int16_t)); +// outf.samples=len; +// ast_write(channels[1], &outf); +// } + if ((f->samples = t38_gateway_tx(&t38_state, f->data.ptr, f->samples))) { + f->datalen = f->samples * sizeof(int16_t); + f->src = "T38Gateway"; + ast_write(channels[1], f); + } + } + ast_frfree(f); + } else { + ast_debug(1, "Error reading frame on %s, stop bridging as the call has finished.\n", active->name); + s->finished=-1; + break; + } + } + } + + t38_gateway_get_transfer_statistics(&t38_state, &t38_stats); + ast_debug(1, "Connection Statistics\n\tBit Rate :%i\n\tECM : %s\n\tPages : %i\n",t38_stats.bit_rate, (t38_stats.error_correcting_mode?"Yes":"No"), t38_stats.pages_transferred); + } else { + ast_log(LOG_ERROR, "T.38 gateway failed to init\n"); + s->finished=-1; + } +} + +static void ast_bridge_frames(fax_session *s) +{ + struct ast_dsp *dsp = NULL; + struct ast_channel *active = NULL; + struct ast_channel *inactive = NULL; + struct ast_channel *channels[2]={s->chan, s->peer}; + enum ast_t38_state activestate, inactivestate; + struct ast_frame *f; + int res, timeout, ftone=0; + struct ast_control_t38_parameters t38_parameters = { .version = 0, + .max_ifp = 800, + .rate = AST_T38_RATE_9600, + .rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF, + .fill_bit_removal = 1, + .transcoding_mmr = 1, + .transcoding_jbig = 1, + }; + + /* Setup DSP CNG/CED processing */ + if ((dsp=ast_dsp_new())) { + ast_dsp_set_features(dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(dsp, DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED); + } else + ast_debug(1, "Unable to allocate Fax Detect DSP This may lead to problems with T.38 switchover!\n"); + + ast_debug(1, "Bridging started chan %s peer %s\n", s->chan->name, s->peer->name); + + for(;;) { + timeout=20; + if ((active = ast_waitfor_n(channels, 2, &timeout))) { + inactive = (active == channels[0]) ? channels[1] : channels[0]; + activestate = ast_channel_get_t38_state(active); + inactivestate = ast_channel_get_t38_state(inactive); + + /* Leave and gateway if both channels are in a stable T.38 (not negotiating) state and only one of them ist negotiated! */ + if ( ((activestate == T38_STATE_NEGOTIATED) && (inactivestate != T38_STATE_NEGOTIATED && inactivestate != T38_STATE_NEGOTIATING)) + || + ((inactivestate == T38_STATE_NEGOTIATED) && (activestate != T38_STATE_NEGOTIATED && activestate != T38_STATE_NEGOTIATING)) + ) + { + ast_debug(1, "Bridging finished for gateway as activestate=%d and inactivestate=%d\n", activestate, inactivestate); + break; + } + if ((f = ast_read(active))) { + /* Check if one of the channels has refused T.38 negotiation. If yes, fake a "NEGOTIATED" frame + * so that the bridged channel will finish T.38 negotiation. Once the negotiation is settled down + * the above code will exit the loop and start T.38 gatewaying. + */ + if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_T38_PARAMETERS)) { + if (f->datalen != sizeof(struct ast_control_t38_parameters)) { + ast_debug(1, "======== invalid T38 control frame on active channel %s\n", active->name); + } else { + struct ast_control_t38_parameters *parameters = f->data.ptr; + if (parameters->request_response == AST_T38_REFUSED) { + ast_debug(1, "Change the AST_T38_REFUSED control message receive from %s into AST_T38_NEGOTIATED and send it to %s!\n", active->name, inactive->name); + parameters->request_response = AST_T38_NEGOTIATED; + parameters->max_ifp = 800; + parameters->version = 0, + parameters->rate = AST_T38_RATE_9600, +// parameters->rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF, +// parameters->fill_bit_removal = 1, +// parameters->transcoding_mmr = 1, +// parameters->transcoding_jbig = 1, + ast_write(inactive, f); + ast_frfree(f); + continue; + } + } + } + + /* Dont send packets to a channel negotiating T.38 ignore them*/ + if ((inactivestate != T38_STATE_NEGOTIATED) && (inactivestate != T38_STATE_NEGOTIATING)) { + if ((dsp) && (inactivestate == T38_STATE_UNKNOWN)) { + f = ast_dsp_process(active, dsp, f); + if ((f->frametype == AST_FRAME_DTMF) && + ((f->subclass == 'f') || (f->subclass == 'e'))) { + switch (f->subclass) { + case 'f':ftone=1;break; + case 'e':ftone=2; + } + /* tickle the channel as we have fax tone */ + ast_debug(1, "Fax %s Tone Detected On %s\n", (ftone == 1) ? "CNG" : "CED", active->name); + t38_parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + res=ast_indicate_data(inactive, AST_CONTROL_T38_PARAMETERS, &t38_parameters, sizeof(t38_parameters)); + ast_debug(1, "T.38 negotiation requested on channel %s returned %d\n", inactive->name, res); + } else { + ast_write(inactive, f); + } + } else + ast_write(inactive, f); + } + ast_frfree(f); + } else { + ast_debug(1, "Error reading frame on %s, stop bridging as the call has finished.\n", active->name); + s->finished=1; + break; + } + } + } + if (dsp) + ast_dsp_free(dsp); +} + /* === Application functions === */ static int sndfax_exec(struct ast_channel *chan, void *data) @@ -915,12 +1224,288 @@ return res; } +/*! + * \brief return the first unlocked cdr in a possible chain (copy-pasted from features.c) + */ +static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr) +{ + struct ast_cdr *cdr_orig = cdr; + while (cdr) { + if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED)) + return cdr; + cdr = cdr->next; + } + return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */ +} + +/*! + * \brief Helper function called from app_dial.c + * \param chan,peer + */ +static int __ast_bridge_t38(struct ast_channel *chan, struct ast_channel *peer) +{ + int res = 0; + fax_session *s; + struct ast_cdr *peer_cdr = pick_unlocked_cdr(peer->cdr); /* the proper chan cdr, if there are forked cdrs */ + + s=ast_malloc(sizeof(fax_session)); + if (!s) { + ast_log(LOG_ERROR, "Error allocating memory, giving up.\n"); + return -1; + } + if (!chan || !peer) { + ast_debug(1, "Fax channel is NULL Or No Fax Handler Possible. Giving up.\n"); + ast_free(s); + return -1; + } + + s->finished = 0; + s->chan = chan; + s->peer = peer; + + pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name); + pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name); + + /* pick up originating call */ + if (chan->_state != AST_STATE_UP) { + res = ast_answer(chan); + if (res) { + ast_debug(1, "Could not answer channel '%s'. Can not run a gateway on a down channel\n", chan->name); + ast_free(s); + return res; + } + } + + manager_event(EVENT_FLAG_CALL, "Dial", + "SubEvent: Begin\r\n" + "Channel: %s\r\n" + "Destination: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "UniqueID: %s\r\n" + "DestUniqueID: %s\r\n" + "Dialstring: %s\r\n", + chan->name, s->peer->name, S_OR(chan->cid.cid_num, ""), + S_OR(chan->cid.cid_name, ""), chan->uniqueid, + s->peer->uniqueid, ""); + + /* Stop monitor and set channels to signed linear codec */ + /* Klaus Darilion: no need to stop the monitor, as only voice frames will be recorded */ + //ast_monitor_stop(chan, 1); + ast_set_write_format(s->chan, AST_FORMAT_SLINEAR); + ast_set_read_format(s->chan, AST_FORMAT_SLINEAR); + ast_set_write_format(s->peer, AST_FORMAT_SLINEAR); + ast_set_read_format(s->peer, AST_FORMAT_SLINEAR); + + ast_channel_make_compatible(s->chan, s->peer); + + /* start the gateway*/ + /* switch over loop*/ + while (s->finished == 0) + { + /* Start bridging packets until all sides have negotiated T.38 capabilities and only one is capable*/ + ast_bridge_frames(s); + /* We are now T.38 One Side Gateway*/ + if (!s->finished) + ast_t38_gateway(s); + } + + if (s->finished < 0) { + ast_debug(1, "Transmission failed or call hung up\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "Channel problems"); + res = -1; + } else if (s->finished > 0) { + ast_debug(1, "Transmission finished Ok\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "PASSED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "OK"); + res = 0; + } else { + ast_log(LOG_WARNING, "Transmission error\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "Transmission error"); + res = -1; + } + + ast_cdr_specialized_reset(peer_cdr,0); + + ast_free(s); + return res; +} + +static int t38gateway_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *parse; + fax_session *s; + int state, priority, timeout; + const char *account=NULL, *cid_name=NULL, *cid_num=NULL, *context=NULL, *exten=NULL; + struct ast_variable *vars=NULL; + struct outgoing_helper oh; + char *number, *tech; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(dest); + AST_APP_ARG(timeout); + ); + + s=ast_malloc(sizeof(fax_session)); + if (!s) { + ast_log(LOG_ERROR, "Error allocating memory, giving up.\n"); + return -1; + } + if (!chan) { + ast_log(LOG_ERROR, "Channel is NULL, giving up.\n"); + ast_free(s); + return -1; + } + + s->finished = 0; + s->chan = chan; + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + /* Get a technology/[device:]number pair */ + number = args.dest; + tech = strsep(&number, "/"); + if (!number) { + ast_debug(1, "dialstring argument takes format (technology/[device:]number1)\n"); + ast_free(s); + return -1; + } + + if (args.timeout) + timeout = atoi(args.timeout) * 1000; + else + timeout = 35000; + + char numsubst[256]; + ast_copy_string(numsubst, number, sizeof(numsubst)); + + /* Setup the outgoing helper and dial waiting for timeout or answer*/ + if (!ast_strlen_zero(chan->cid.cid_num)) + cid_num=ast_strdupa(chan->cid.cid_num); + if (!ast_strlen_zero(chan->cid.cid_name)) + cid_name=ast_strdupa(chan->cid.cid_name); + if (!ast_strlen_zero(chan->context)) + context=ast_strdupa(chan->context); + if (!ast_strlen_zero(chan->exten)) + exten=ast_strdupa(chan->exten); + priority=chan->priority; + + oh.context = context; + oh.exten = exten; + oh.priority = priority; + oh.cid_num = cid_num; + oh.cid_name = cid_name; + oh.account = account; + oh.vars = vars; + oh.parent_channel = chan; + + oh.parent_channel=chan; + if (!(s->peer = __ast_request_and_dial(tech, AST_FORMAT_SLINEAR, numsubst, timeout, &state, chan->cid.cid_num, chan->cid.cid_name, &oh))) { + chan->hangupcause = state; + ast_free(s); + return -1; + } + ast_debug(1, "Outgoing channel '%s'\n", s->peer->name); + + /* pick up originating call */ + if (chan->_state != AST_STATE_UP) { + ast_debug(1, "Answering channel '%s'\n", chan->name); + res = ast_answer(chan); + if (res) { + ast_debug(1, "Could not answer channel '%s' cant run a gateway on a down channel\n", chan->name); + ast_free(s); + return res; + } + } + + pbx_builtin_setvar_helper(chan, "DIALSTATUS", ast_strdup(ast_cause2str(state))); + manager_event(EVENT_FLAG_CALL, "Dial", + "SubEvent: Begin\r\n" + "Channel: %s\r\n" + "Destination: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "UniqueID: %s\r\n" + "DestUniqueID: %s\r\n" + "Dialstring: %s\r\n", + chan->name, s->peer->name, S_OR(chan->cid.cid_num, ""), + S_OR(chan->cid.cid_name, ""), chan->uniqueid, + s->peer->uniqueid, number ? number : ""); + + if (chan->cdr) + ast_cdr_setdestchan(chan->cdr, s->peer->name); + + /* Inherit context and extension */ + if (!ast_strlen_zero(chan->macrocontext)) + ast_string_field_set(s->peer, dialcontext, chan->macrocontext); + if (!ast_strlen_zero(chan->macroexten)) + ast_copy_string(s->peer->exten, chan->macroexten, sizeof(s->peer->exten)); + s->peer->macropriority=chan->macropriority; + + /* Clear all channel variables which to be set by the application. + Pre-set status to error so in case of any problems we can just leave */ + pbx_builtin_setvar_helper(s->peer, "DIALEDPEERNUMBER", numsubst); + + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "Channel problems"); + + /* Stop monitor and set channels to signed linear codec */ +// ast_monitor_stop(chan, 1); + ast_set_write_format(s->chan, AST_FORMAT_SLINEAR); + ast_set_read_format(s->chan, AST_FORMAT_SLINEAR); + ast_set_write_format(s->peer, AST_FORMAT_SLINEAR); + ast_set_read_format(s->peer, AST_FORMAT_SLINEAR); + + ast_channel_make_compatible(s->chan, s->peer); + + /* start the gateway*/ + /* switch over loop*/ + while (s->finished == 0) + { + /* Start bridging packets until all sides have negotiated T.38 capabilities and only one is capable*/ + ast_bridge_frames(s); + /* We are now T.38 One Side Gateway*/ + if (!s->finished) + ast_t38_gateway(s); + } + + if (s->finished < 0) { + ast_log(LOG_WARNING, "Transmission failed\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "Channel problems"); + res = -1; + } else if (s->finished > 0) { + ast_debug(1, "Transmission finished Ok\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "PASSED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "OK"); + res = 0; + } else { + ast_log(LOG_WARNING, "Transmission error\n"); + pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); + pbx_builtin_setvar_helper(chan, "FAXERROR", "Transmission error"); + res = -1; + } + if (s->peer) { + ast_hangup(s->peer); + } + ast_free(s); + return res; +} + static int unload_module(void) { int res; + /* Unpublish __ast_bridge_t38 function */ + ast_bridge_t38 = NULL; + res = ast_unregister_application(app_sndfax_name); res |= ast_unregister_application(app_rcvfax_name); + res |= ast_unregister_application(app_t38gateway_name); return res; } @@ -929,8 +1514,12 @@ { int res ; + /* Publish __ast_bridge_t38 function to be used by Dial application */ + ast_bridge_t38 = __ast_bridge_t38; + res = ast_register_application_xml(app_sndfax_name, sndfax_exec); res |= ast_register_application_xml(app_rcvfax_name, rcvfax_exec); + res |= ast_register_application_xml(app_t38gateway_name, t38gateway_exec); /* The default SPAN message handler prints to stderr. It is something we do not want */ span_set_message_handler(NULL); diff -r -u asterisk-1.6.2.6/channels/chan_iax2.c asterisk-1.6.2.6-t38gw/channels/chan_iax2.c --- asterisk-1.6.2.6/channels/chan_iax2.c 2010-03-03 19:05:00.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/channels/chan_iax2.c 2010-03-15 10:33:32.000000000 +0100 @@ -5362,6 +5362,12 @@ ast_moh_stop(c); goto done; } + case AST_CONTROL_T38_PARAMETERS: + /* Immediately signal back that T38 is not supported. Otherwise + * the bridged channel has to wait until a timeout. + */ + res = -1; + goto done; } res = send_command(pvt, AST_FRAME_CONTROL, condition, 0, data, datalen, -1); diff -r -u asterisk-1.6.2.6/channels/chan_sip.c asterisk-1.6.2.6-t38gw/channels/chan_sip.c --- asterisk-1.6.2.6/channels/chan_sip.c 2010-03-03 01:19:57.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/channels/chan_sip.c 2010-03-15 10:33:32.000000000 +0100 @@ -5050,7 +5050,7 @@ case T38_DISABLED: if (old == T38_ENABLED) { parameters.request_response = AST_T38_TERMINATED; - } else if (old == T38_LOCAL_REINVITE) { + } else if (old == T38_LOCAL_REINVITE || old == T38_DISABLED) { parameters.request_response = AST_T38_REFUSED; } break; @@ -6424,6 +6424,7 @@ static void interpret_t38_parameters(struct sip_pvt *p, const struct ast_control_t38_parameters *parameters) { if (!ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) { + change_t38_state(p, T38_DISABLED); return; } switch (parameters->request_response) { @@ -6493,6 +6494,8 @@ struct sip_pvt *p = ast->tech_pvt; int res = 0; + ast_debug(1, "Requested indication %d on channel %s\n", condition, ast->name); + sip_pvt_lock(p); switch(condition) { case AST_CONTROL_RINGING: @@ -6573,6 +6576,12 @@ } else { const struct ast_control_t38_parameters *parameters = data; interpret_t38_parameters(p, parameters); + if (!ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) { + /* Explicitly signal back that the T.38 request was not handled + * (to avoid timeouts on bridged channels) + */ + res = -1; + } } break; case AST_CONTROL_SRCUPDATE: diff -r -u asterisk-1.6.2.6/include/asterisk/features.h asterisk-1.6.2.6-t38gw/include/asterisk/features.h --- asterisk-1.6.2.6/include/asterisk/features.h 2009-02-04 22:17:53.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/include/asterisk/features.h 2010-03-15 15:41:34.000000000 +0100 @@ -107,6 +107,12 @@ /*! \brief Determine system call pickup extension */ const char *ast_pickup_ext(void); +/*! + * \brief Bridge a call with fax gateway capabilities. + * Function is provided by app_fax.so and used in app_dial.so +*/ +extern int (*ast_bridge_t38)(struct ast_channel *chan,struct ast_channel *peer); + /*! \brief Bridge a call, optionally allowing redirection */ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer,struct ast_bridge_config *config); diff -r -u asterisk-1.6.2.6/main/channel.c asterisk-1.6.2.6-t38gw/main/channel.c --- asterisk-1.6.2.6/main/channel.c 2010-02-16 18:10:42.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/main/channel.c 2010-03-15 10:33:32.000000000 +0100 @@ -3197,13 +3197,16 @@ /* deprecated T.38 control frame */ return -1; case AST_CONTROL_T38_PARAMETERS: - /* there is no way to provide 'default' behavior for these - * control frames, so we need to return failure, but there - * is also no value in the log message below being emitted - * since failure to handle these frames is not an 'error' - * so just return right now. - */ - return -1; + /* The channel does not know how to handle T38: Explicitly + * refuse the T38 request, so that the bridged channel can perform + * fallback to G.711 or an T.38-Gatewaying application can translate + */ + { + struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REFUSED }; + ast_debug(1, "Channel %s was unable to handle indication %d, faking AST_T38_REFUSED\n", chan->name, condition); + ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); + } + return 0; case AST_CONTROL_RINGING: ts = ast_get_indication_tone(chan->zone, "ring"); /* It is common practice for channel drivers to return -1 if trying @@ -3466,6 +3469,16 @@ case AST_FRAME_CONTROL: res = (chan->tech->indicate == NULL) ? 0 : chan->tech->indicate(chan, fr->subclass, fr->data.ptr, fr->datalen); + if ( res && (fr->subclass==AST_CONTROL_T38_PARAMETERS) ) { + /* The channel does not know how to handle T38: Explicitly + * refuse the T38 request, so that the bridged channel can perform + * fallback to G.711 or an T.38-Gatewaying application can translate + */ + struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REFUSED }; + ast_debug(1, "Channel %s was unable to handle AST_CONTROL_T38_PARAMETERS indication, faking AST_T38_REFUSED\n", chan->name); + ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); + res = 0; + } break; case AST_FRAME_DTMF_BEGIN: if (chan->audiohooks) { diff -r -u asterisk-1.6.2.6/main/features.c asterisk-1.6.2.6-t38gw/main/features.c --- asterisk-1.6.2.6/main/features.c 2010-03-02 20:26:35.000000000 +0100 +++ asterisk-1.6.2.6-t38gw/main/features.c 2010-03-15 15:42:23.000000000 +0100 @@ -2430,6 +2430,14 @@ } /*! + * \brief bridge the call with fax gateway capabilities. + * \param chan,peer + * + * Function is provided by app_fax.so and used in app_dial.so + */ +int (*ast_bridge_t38)(struct ast_channel *chan,struct ast_channel *peer) = NULL; + +/*! * \brief bridge the call and set CDR * \param chan,peer,config *