diff options
author | Matthew Nicholson <mnicholson@digium.com> | 2011-06-30 18:22:28 +0000 |
---|---|---|
committer | Matthew Nicholson <mnicholson@digium.com> | 2011-06-30 18:22:28 +0000 |
commit | 0f0956e67a86222298a06a01bf5f19e0bc219283 (patch) | |
tree | 2fe2a4f50430e0ba19e13a213fd366e4dcef8ba6 /res | |
parent | 82d28452cac71a9ad1948714d541c96e96a8e7b4 (diff) |
Fax gateway functionality (i.e. translating between a T.30 terminal and a T.38
terminal). Can be enabled on a channel by setting FAXOPT(gateway)=yes in the
dialplan.
Big thanks to irroot for porting this code to use the framehooks api.
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@325816 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res')
-rw-r--r-- | res/res_fax.c | 887 | ||||
-rw-r--r-- | res/res_fax_spandsp.c | 308 |
2 files changed, 1114 insertions, 81 deletions
diff --git a/res/res_fax.c b/res/res_fax.c index e323fba65..63c53679d 100644 --- a/res/res_fax.c +++ b/res/res_fax.c @@ -5,6 +5,23 @@ * * Dwayne M. Hubbard <dhubbard@digium.com> * Kevin P. Fleming <kpfleming@digium.com> + * Matthew Nicholson <mnicholson@digium.com> + * + * Initial T.38-gateway code + * 2008, Daniel Ferenci <daniel.ferenci@nethemba.com> + * 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 <gregory@dnstelecom.co.za> + * dns Telecom http://www.dnstelecom.co.za + * + * Modified to make T.38-gateway compatible with Asterisk 1.6.2 + * 2010, Anton Verevkin <mymail@verevkin.it> + * ViaNetTV http://www.vianettv.com + * + * Modified to make T.38-gateway work + * 2010, Klaus Darilion, IPCom GmbH, www.ipcom.at * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -27,6 +44,7 @@ * * \author Dwayne M. Hubbard <dhubbard@digium.com> * \author Kevin P. Fleming <kpfleming@digium.com> + * \author Matthew Nicholson <mnicholson@digium.com> * * A generic FAX resource module that provides SendFAX and ReceiveFAX applications. * This module requires FAX technology modules, like res_fax_spandsp, to register with it @@ -58,6 +76,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/dsp.h" #include "asterisk/indications.h" #include "asterisk/ast_version.h" +#include "asterisk/translate.h" /*** DOCUMENTATION <application name="ReceiveFax" language="en_US"> @@ -165,6 +184,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <enum name="modem"> <para>R/W Modem type (v17/v27/v29).</para> </enum> + <enum name="gateway"> + <para>R/W T38 Gateway Enabled (yes/no)</para> + </enum> <enum name="pages"> <para>R/O Number of pages transferred.</para> </enum> @@ -215,12 +237,37 @@ struct ast_fax_debug_info { struct ast_dsp *dsp; }; +/*! \brief used for gateway framehook */ +struct fax_gateway { + /*! \brief FAX Session */ + struct ast_fax_session *s; + /*! \brief reserved fax session token */ + struct ast_fax_tech_token *token; + /*! \brief the start of our timeout counter */ + struct timeval timeout_start; + /*! \brief DSP Processor */ + struct ast_dsp *chan_dsp; + struct ast_dsp *peer_dsp; + /*! \brief framehook used in gateway mode */ + int framehook; + /*! \brief bridged */ + int bridged:1; + /*! \brief a flag to track the state of our negotiation */ + enum ast_t38_state t38_state; + /*! \brief original audio formats */ + struct ast_format chan_read_format; + struct ast_format chan_write_format; + struct ast_format peer_read_format; + struct ast_format peer_write_format; +}; + static int fax_logger_level = -1; /*! \brief maximum buckets for res_fax ao2 containers */ #define FAX_MAXBUCKETS 10 #define RES_FAX_TIMEOUT 10000 +#define FAX_GATEWAY_TIMEOUT RES_FAX_TIMEOUT /*! \brief The faxregistry is used to manage information and statistics for all FAX sessions. */ static struct { @@ -397,10 +444,40 @@ static struct ast_fax_session_details *session_details_new(void) d->modems = general_options.modems; d->minrate = general_options.minrate; d->maxrate = general_options.maxrate; + d->gateway_id = -1; return d; } +static struct ast_control_t38_parameters our_t38_parameters = { + .version = 0, + .max_ifp = 400, + .rate = AST_T38_RATE_14400, + .rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF, +}; + +static void t38_parameters_ast_to_fax(struct ast_fax_t38_parameters *dst, const struct ast_control_t38_parameters *src) +{ + dst->version = src->version; + dst->max_ifp = src->max_ifp; + dst->rate = src->rate; + dst->rate_management = src->rate_management; + dst->fill_bit_removal = src->fill_bit_removal; + dst->transcoding_mmr = src->transcoding_mmr; + dst->transcoding_jbig = src->transcoding_jbig; +} + +static void t38_parameters_fax_to_ast(struct ast_control_t38_parameters *dst, const struct ast_fax_t38_parameters *src) +{ + dst->version = src->version; + dst->max_ifp = src->max_ifp; + dst->rate = src->rate; + dst->rate_management = src->rate_management; + dst->fill_bit_removal = src->fill_bit_removal; + dst->transcoding_mmr = src->transcoding_mmr; + dst->transcoding_jbig = src->transcoding_jbig; +} + /*! \brief returns a reference counted details structure from the channel's fax datastore. If the datastore * does not exist it will be created */ static struct ast_fax_session_details *find_or_create_details(struct ast_channel *chan) @@ -423,6 +500,11 @@ static struct ast_fax_session_details *find_or_create_details(struct ast_channel } /* add the datastore to the channel and increment the refcount */ datastore->data = details; + + /* initialize default T.38 parameters */ + t38_parameters_ast_to_fax(&details->our_t38_parameters, &our_t38_parameters); + t38_parameters_ast_to_fax(&details->their_t38_parameters, &our_t38_parameters); + ao2_ref(details, 1); ast_channel_lock(chan); ast_channel_datastore_add(chan, datastore); @@ -645,6 +727,19 @@ static unsigned int fax_rate_str_to_int(const char *ratestr) } } +/*! \brief Release a session token. + * \param s a session returned from fax_session_reserve() + * \param token a token generated from fax_session_reserve() + * + * This function releases the given token and marks the given session as no + * longer reserved. It is safe to call on a session that is not actually + * reserved and with a NULL token. This is so that sessions returned by + * technologies that do not support reserved sessions don't require extra logic + * to handle. + * + * \note This function DOES NOT release the given fax session, only the given + * token. + */ static void fax_session_release(struct ast_fax_session *s, struct ast_fax_tech_token *token) { if (token) { @@ -691,6 +786,19 @@ static void destroy_session(void *session) ast_free(s->chan_uniqueid); } +/*! \brief Reserve a fax session. + * \param details the fax session details + * \param token a pointer to a place to store a token to be passed to fax_session_new() later + * + * This function reserves a fax session for use later. If the selected fax + * technology does not support reserving sessions a session will still be + * returned but token will not be set. + * + * \note The reference returned by this function does not get consumed by + * fax_session_new() and must always be dereferenced separately. + * + * \return NULL or an uninitialized and possibly reserved session + */ static struct ast_fax_session *fax_session_reserve(struct ast_fax_session_details *details, struct ast_fax_tech_token **token) { struct ast_fax_session *s; @@ -701,6 +809,8 @@ static struct ast_fax_session *fax_session_reserve(struct ast_fax_session_detail } s->state = AST_FAX_STATE_INACTIVE; + s->details = details; + ao2_ref(s->details, 1); /* locate a FAX technology module that can handle said requirements * Note: the requirements have not yet been finalized as T.38 @@ -739,7 +849,22 @@ static struct ast_fax_session *fax_session_reserve(struct ast_fax_session_detail return s; } -/*! \brief create a FAX session */ +/*! \brief create a FAX session + * + * \param details details for the session + * \param chan the channel the session will run on + * \param reserved a reserved session to base this session on (can be NULL) + * \param token the token for a reserved session (can be NULL) + * + * Create a new fax session based on the given details structure. + * + * \note The given token is always consumed (by tech->new_session() or by + * fax_session_release() in the event of a failure). The given reference to a + * reserved session is never consumed and must be dereferenced separately from + * the reference returned by this function. + * + * \return NULL or a reference to a new fax session + */ static struct ast_fax_session *fax_session_new(struct ast_fax_session_details *details, struct ast_channel *chan, struct ast_fax_session *reserved, struct ast_fax_tech_token *token) { struct ast_fax_session *s = NULL; @@ -749,6 +874,12 @@ static struct ast_fax_session *fax_session_new(struct ast_fax_session_details *d s = reserved; ao2_ref(reserved, +1); + /* NOTE: we don't consume the reference to the reserved + * session. The session returned from fax_session_new() is a + * new reference and must be derefed in addition to the + * reserved session. + */ + if (s->state == AST_FAX_STATE_RESERVED) { ast_atomic_fetchadd_int(&faxregistry.reserved_sessions, -1); s->state = AST_FAX_STATE_UNINITIALIZED; @@ -791,8 +922,10 @@ static struct ast_fax_session *fax_session_new(struct ast_fax_session_details *d } s->chan = chan; - s->details = details; - ao2_ref(s->details, 1); + if (!s->details) { + s->details = details; + ao2_ref(s->details, 1); + } details->id = s->id = ast_atomic_fetchadd_int(&faxregistry.nextsessionname, 1); @@ -892,9 +1025,6 @@ static char *generate_filenames_string(struct ast_fax_session_details *details, static int report_fax_status(struct ast_channel *chan, struct ast_fax_session_details *details, const char *status) { char *filenames = generate_filenames_string(details, "FileName: ", "\r\n"); - if (!filenames) { - return 1; - } ast_channel_lock(chan); if (details->option.statusevents) { @@ -902,24 +1032,30 @@ static int report_fax_status(struct ast_channel *chan, struct ast_fax_session_de get_manager_event_info(chan, &info); manager_event(EVENT_FLAG_CALL, - (details->caps & AST_FAX_TECH_RECEIVE) ? "ReceiveFAXStatus" : "SendFAXStatus", + "FAXStatus", + "Operation: %s\r\n" "Status: %s\r\n" "Channel: %s\r\n" "Context: %s\r\n" "Exten: %s\r\n" "CallerID: %s\r\n" "LocalStationID: %s\r\n" - "%s\r\n", + "%s%s", + (details->caps & AST_FAX_TECH_GATEWAY) ? "gateway" : (details->caps & AST_FAX_TECH_RECEIVE) ? "receive" : "send", status, chan->name, info.context, info.exten, info.cid, details->localstationid, - filenames); + S_OR(filenames, ""), + filenames ? "\r\n" : ""); } ast_channel_unlock(chan); - ast_free(filenames); + + if (filenames) { + ast_free(filenames); + } return 0; } @@ -963,28 +1099,6 @@ static void set_channel_variables(struct ast_channel *chan, struct ast_fax_sessi GENERIC_FAX_EXEC_ERROR_QUIET(fax, chan, errorstr, reason); \ } while (0) -static void t38_parameters_ast_to_fax(struct ast_fax_t38_parameters *dst, const struct ast_control_t38_parameters *src) -{ - dst->version = src->version; - dst->max_ifp = src->max_ifp; - dst->rate = src->rate; - dst->rate_management = src->rate_management; - dst->fill_bit_removal = src->fill_bit_removal; - dst->transcoding_mmr = src->transcoding_mmr; - dst->transcoding_jbig = src->transcoding_jbig; -} - -static void t38_parameters_fax_to_ast(struct ast_control_t38_parameters *dst, const struct ast_fax_t38_parameters *src) -{ - dst->version = src->version; - dst->max_ifp = src->max_ifp; - dst->rate = src->rate; - dst->rate_management = src->rate_management; - dst->fill_bit_removal = src->fill_bit_removal; - dst->transcoding_mmr = src->transcoding_mmr; - dst->transcoding_jbig = src->transcoding_jbig; -} - static int set_fax_t38_caps(struct ast_channel *chan, struct ast_fax_session_details *details) { switch (ast_channel_get_t38_state(chan)) { @@ -995,6 +1109,8 @@ static int set_fax_t38_caps(struct ast_channel *chan, struct ast_fax_session_det case T38_STATE_UNAVAILABLE: details->caps |= AST_FAX_TECH_AUDIO; break; + case T38_STATE_NEGOTIATED: + /* already in T.38 mode? This should not happen. */ case T38_STATE_NEGOTIATING: { /* the other end already sent us a T.38 reinvite, so we need to prod the channel * driver into resending their parameters to us if it supports doing so... if @@ -1076,13 +1192,6 @@ static int disable_t38(struct ast_channel *chan) return 0; } -static struct ast_control_t38_parameters our_t38_parameters = { - .version = 0, - .max_ifp = 400, - .rate = AST_T38_RATE_14400, - .rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF, -}; - /*! \brief this is the generic FAX session handling function */ static int generic_fax_exec(struct ast_channel *chan, struct ast_fax_session_details *details, struct ast_fax_session *reserved, struct ast_fax_tech_token *token) { @@ -1350,8 +1459,6 @@ static int receivefax_t38_init(struct ast_channel *chan, struct ast_fax_session_ struct ast_frame *frame = NULL; struct ast_control_t38_parameters t38_parameters; - t38_parameters_ast_to_fax(&details->our_t38_parameters, &our_t38_parameters); - /* don't send any audio if we've already received a T.38 reinvite */ if (ast_channel_get_t38_state(chan) != T38_STATE_NEGOTIATING) { /* generate 3 seconds of CED */ @@ -1535,6 +1642,14 @@ static int receivefax_exec(struct ast_channel *chan, const char *data) ast_string_field_set(details, error, "INIT_ERROR"); set_channel_variables(chan, details); + if (details->caps & AST_FAX_TECH_GATEWAY) { + ast_string_field_set(details, resultstr, "can't receive a fax on a channel with a T.38 gateway"); + set_channel_variables(chan, details); + ast_log(LOG_ERROR, "executing ReceiveFAX on a channel with a T.38 Gateway is not supported\n"); + ao2_ref(details, -1); + return -1; + } + if (details->maxrate < details->minrate) { ast_string_field_set(details, error, "INVALID_ARGUMENTS"); ast_string_field_set(details, resultstr, "maxrate is less than minrate"); @@ -1741,8 +1856,6 @@ static int sendfax_t38_init(struct ast_channel *chan, struct ast_fax_session_det struct ast_frame *frame = NULL; struct ast_control_t38_parameters t38_parameters; - t38_parameters_ast_to_fax(&details->our_t38_parameters, &our_t38_parameters); - /* send CNG tone while listening for the receiver to initiate a switch * to T.38 mode; if they do, stop sending the CNG tone and proceed with * the switch. @@ -1999,6 +2112,14 @@ static int sendfax_exec(struct ast_channel *chan, const char *data) ast_string_field_set(details, error, "INIT_ERROR"); set_channel_variables(chan, details); + if (details->caps & AST_FAX_TECH_GATEWAY) { + ast_string_field_set(details, resultstr, "can't send a fax on a channel with a T.38 gateway"); + set_channel_variables(chan, details); + ast_log(LOG_ERROR, "executing SendFAX on a channel with a T.38 Gateway is not supported\n"); + ao2_ref(details, -1); + return -1; + } + if (details->maxrate < details->minrate) { ast_string_field_set(details, error, "INVALID_ARGUMENTS"); ast_string_field_set(details, resultstr, "maxrate is less than minrate"); @@ -2230,6 +2351,647 @@ static int sendfax_exec(struct ast_channel *chan, const char *data) return (!channel_alive) ? -1 : 0; } +/*! \brief destroy a FAX gateway session structure */ +static void destroy_gateway(void *data) +{ + struct fax_gateway *gateway = data; + + if (gateway->chan_dsp) { + ast_dsp_free(gateway->chan_dsp); + gateway->chan_dsp = NULL; + } + + if (gateway->peer_dsp) { + ast_dsp_free(gateway->peer_dsp); + gateway->peer_dsp = NULL; + } + + if (gateway->s) { + fax_session_release(gateway->s, gateway->token); + gateway->token = NULL; + gateway->s->details->caps |= ~AST_FAX_TECH_GATEWAY; + + ao2_lock(faxregistry.container); + ao2_unlink(faxregistry.container, gateway->s); + ao2_unlock(faxregistry.container); + + ao2_ref(gateway->s, -1); + gateway->s = NULL; + } +} + +/*! \brief Create a new fax gateway object. + * \param details the fax session details + * \return NULL or a fax gateway object + */ +static struct fax_gateway *fax_gateway_new(struct ast_fax_session_details *details) +{ + struct fax_gateway *gateway = ao2_alloc(sizeof(*gateway), destroy_gateway); + if (!gateway) { + return NULL; + } + + gateway->chan_dsp = ast_dsp_new(); + if (!gateway->chan_dsp) { + ao2_ref(gateway, -1); + return NULL; + } + + gateway->peer_dsp = ast_dsp_new(); + if (!gateway->peer_dsp) { + ao2_ref(gateway, -1); + return NULL; + } + + gateway->framehook = -1; + + ast_dsp_set_features(gateway->chan_dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(gateway->chan_dsp, DSP_FAXMODE_DETECT_CED); + + ast_dsp_set_features(gateway->peer_dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(gateway->peer_dsp, DSP_FAXMODE_DETECT_CED); + + details->caps = AST_FAX_TECH_GATEWAY; + if (!(gateway->s = fax_session_reserve(details, &gateway->token))) { + details->caps |= ~AST_FAX_TECH_GATEWAY; + ast_log(LOG_ERROR, "Can't reserve a FAX session, gateway attempt failed.\n"); + ao2_ref(gateway, -1); + return NULL; + } + + return gateway; +} + +/*! \brief Create a fax session and start T.30<->T.38 gateway mode + * \param gateway a fax gateway object + * \param details fax session details + * \param chan active channel + * \return 0 on error 1 on success*/ +static int fax_gateway_start(struct fax_gateway *gateway, struct ast_fax_session_details *details, struct ast_channel *chan) +{ + struct ast_fax_session *s; + + /* create the FAX session */ + if (!(s = fax_session_new(details, chan, gateway->s, gateway->token))) { + gateway->token = NULL; + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "error starting gateway session"); + ast_string_field_set(details, error, "INIT_ERROR"); + set_channel_variables(chan, details); + report_fax_status(chan, details, "No Available Resource"); + ast_log(LOG_ERROR, "Can't create a FAX session, gateway attempt failed.\n"); + return -1; + } + /* release the reference for the reserved session and replace it with + * the real session */ + ao2_ref(gateway->s, -1); + gateway->s = s; + gateway->token = NULL; + + if (gateway->s->tech->start_session(gateway->s) < 0) { + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "error starting gateway session"); + ast_string_field_set(details, error, "INIT_ERROR"); + set_channel_variables(chan, details); + return -1; + } + + gateway->timeout_start.tv_sec = 0; + gateway->timeout_start.tv_usec = 0; + + report_fax_status(chan, details, "FAX Transmission In Progress"); + + return 0; +} + +static struct ast_frame *fax_gateway_detect_ced(struct fax_gateway *gateway, struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *active, struct ast_frame *f) +{ + struct ast_frame *dfr = ast_frdup(f); + struct ast_dsp *active_dsp = (active == chan) ? gateway->chan_dsp : gateway->peer_dsp; + struct ast_channel *other = (active == chan) ? peer : chan; + + if (!dfr) { + return f; + } + + if (!(dfr = ast_dsp_process(active, active_dsp, dfr))) { + return f; + } + + if (dfr->frametype == AST_FRAME_DTMF && dfr->subclass.integer == 'e') { + if (ast_channel_get_t38_state(other) == T38_STATE_UNKNOWN) { + struct ast_control_t38_parameters t38_parameters = { + .request_response = AST_T38_REQUEST_NEGOTIATE, + }; + struct ast_frame control_frame = { + .src = "res_fax", + .frametype = AST_FRAME_CONTROL, + .datalen = sizeof(t38_parameters), + .subclass.integer = AST_CONTROL_T38_PARAMETERS, + .data.ptr = &t38_parameters, + }; + + struct ast_fax_session_details *details = find_details(chan); + ast_frfree(dfr); + + if (!details) { + ast_log(LOG_ERROR, "no FAX session details found on chan %s for T.38 gateway session, odd\n", chan->name); + ast_framehook_detach(chan, gateway->framehook); + return f; + } + + t38_parameters_fax_to_ast(&t38_parameters, &details->our_t38_parameters); + ao2_ref(details, -1); + + if (!(dfr = ast_frisolate(&control_frame))) { + ast_log(LOG_ERROR, "error generating T.38 request control frame on chan %s for T.38 gateway session\n", chan->name); + return f; + } + + gateway->t38_state = T38_STATE_NEGOTIATING; + gateway->timeout_start = ast_tvnow(); + + ast_debug(1, "detected CED tone on %s, requesting T.38 on %s for T.38 gateway session\n", active->name, other->name); + return dfr; + } else { + ast_debug(1, "detected CED tone on %s, but %s does not support T.38 for T.38 gateway session\n", active->name, other->name); + } + } + + ast_frfree(dfr); + return f; +} + +static int fax_gateway_indicate_t38(struct ast_channel *chan, struct ast_channel *active, struct ast_control_t38_parameters *control_params) +{ + if (active == chan) { + return ast_indicate_data(chan, AST_CONTROL_T38_PARAMETERS, control_params, sizeof(*control_params)); + } else { + return ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, control_params, sizeof(*control_params)); + } +} + +/*! \brief T38 Gateway Negotiate t38 parameters + * \param gateway gateway object + * \param chan channel running the gateway + * \param peer channel im bridged too + * \param active channel the frame originated on + * \param f the control frame to process + * \return processed control frame or null frame + */ +static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *active, struct ast_frame *f) +{ + struct ast_control_t38_parameters *control_params = f->data.ptr; + struct ast_channel *other = (active == chan) ? peer : chan; + struct ast_fax_session_details *details; + + if (f->datalen != sizeof(struct ast_control_t38_parameters)) { + /* invalaid AST_CONTROL_T38_PARAMETERS frame, we can't + * do anything with it, pass it on */ + return f; + } + + /* ignore frames from ourselves */ + if ((gateway->t38_state == T38_STATE_NEGOTIATED && control_params->request_response == AST_T38_NEGOTIATED) + || (gateway->t38_state == T38_STATE_REJECTED && control_params->request_response == AST_T38_REFUSED) + || (gateway->t38_state == T38_STATE_NEGOTIATING && control_params->request_response == AST_T38_REQUEST_TERMINATE)) { + + return f; + } + + if (!(details = find_details(chan))) { + ast_log(LOG_ERROR, "no FAX session details found on chan %s for T.38 gateway session, odd\n", chan->name); + ast_framehook_detach(chan, gateway->framehook); + return f; + } + + if (control_params->request_response == AST_T38_REQUEST_NEGOTIATE) { + enum ast_t38_state state = ast_channel_get_t38_state(other); + if (state == T38_STATE_UNKNOWN) { + /* we detected a request to negotiate T.38 and the + * other channel appears to support T.38, we'll pass + * the request through and only step in if the other + * channel rejects the request */ + ast_debug(1, "%s is attempting to negotiate T.38 with %s, we'll see what happens\n", active->name, other->name); + t38_parameters_ast_to_fax(&details->their_t38_parameters, control_params); + gateway->t38_state = T38_STATE_UNKNOWN; + gateway->timeout_start = ast_tvnow(); + ao2_ref(details, -1); + return f; + } else if (state == T38_STATE_UNAVAILABLE || state == T38_STATE_REJECTED) { + /* the other channel does not support T.38, we need to + * step in here */ + ast_debug(1, "%s is attempting to negotiate T.38 but %s does not support it\n", active->name, other->name); + ast_debug(1, "starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", active->name, other->name); + + t38_parameters_ast_to_fax(&details->their_t38_parameters, control_params); + t38_parameters_fax_to_ast(control_params, &details->our_t38_parameters); + + if (fax_gateway_start(gateway, details, chan)) { + ast_log(LOG_ERROR, "error starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", active->name, other->name); + gateway->t38_state = T38_STATE_REJECTED; + control_params->request_response = AST_T38_REFUSED; + + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + } else { + gateway->t38_state = T38_STATE_NEGOTIATED; + control_params->request_response = AST_T38_NEGOTIATED; + report_fax_status(chan, details, "T.38 Negotiated"); + } + + fax_gateway_indicate_t38(chan, active, control_params); + + ao2_ref(details, -1); + return &ast_null_frame; + } else if (gateway->t38_state == T38_STATE_NEGOTIATING) { + /* we got a request to negotiate T.38 after we already + * sent one to the other party based on CED tone + * detection. We'll just pretend we passed this request + * through in the first place. */ + + t38_parameters_ast_to_fax(&details->their_t38_parameters, control_params); + gateway->t38_state = T38_STATE_UNKNOWN; + gateway->timeout_start = ast_tvnow(); + + ast_debug(1, "%s is attempting to negotiate T.38 after we already sent a negotiation request based on CED detection\n", active->name); + ao2_ref(details, -1); + return &ast_null_frame; + } else if (gateway->t38_state == T38_STATE_NEGOTIATED) { + /* we got a request to negotiate T.38 after we already + * sent one to the other party based on CED tone + * detection and received a response. We need to + * respond to this and shut down the gateway. */ + + t38_parameters_fax_to_ast(control_params, &details->their_t38_parameters); + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + + control_params->request_response = AST_T38_NEGOTIATED; + + fax_gateway_indicate_t38(chan, active, control_params); + + ast_string_field_set(details, result, "SUCCESS"); + ast_string_field_set(details, resultstr, "no gateway necessary"); + ast_string_field_set(details, error, "NATIVE_T38"); + set_channel_variables(chan, details); + + ast_debug(1, "%s is attempting to negotiate T.38 after we already negotiated T.38 with %s, disabling the gateway\n", active->name, other->name); + ao2_ref(details, -1); + return &ast_null_frame; + } else { + ast_log(LOG_WARNING, "%s is attempting to negotiate T.38 while %s is in an unsupported state\n", active->name, other->name); + ao2_ref(details, -1); + return f; + } + } else if (gateway->t38_state == T38_STATE_NEGOTIATING + && control_params->request_response == AST_T38_REFUSED) { + + ast_debug(1, "unable to negotiate T.38 on %s for fax gateway\n", active->name); + + /* our request to negotiate T.38 was refused, if the other + * channel supports T.38, they might still reinvite and save + * the day. Otherwise disable the gateway. */ + if (ast_channel_get_t38_state(other) == T38_STATE_UNKNOWN) { + gateway->t38_state = T38_STATE_UNAVAILABLE; + } else { + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "unable to negotiate T.38"); + ast_string_field_set(details, error, "T38_NEG_ERROR"); + set_channel_variables(chan, details); + } + + ao2_ref(details, -1); + return &ast_null_frame; + } else if (gateway->t38_state == T38_STATE_NEGOTIATING + && control_params->request_response == AST_T38_NEGOTIATED) { + + ast_debug(1, "starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", active->name, other->name); + + t38_parameters_ast_to_fax(&details->their_t38_parameters, control_params); + + if (fax_gateway_start(gateway, details, chan)) { + ast_log(LOG_ERROR, "error starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", active->name, other->name); + gateway->t38_state = T38_STATE_NEGOTIATING; + control_params->request_response = AST_T38_REQUEST_TERMINATE; + + fax_gateway_indicate_t38(chan, active, control_params); + } else { + gateway->t38_state = T38_STATE_NEGOTIATED; + report_fax_status(chan, details, "T.38 Negotiated"); + } + + ao2_ref(details, -1); + return &ast_null_frame; + } else if (control_params->request_response == AST_T38_REFUSED) { + /* the other channel refused the request to negotiate T.38, + * we'll step in here and pretend the request was accepted */ + + ast_debug(1, "%s attempted to negotiate T.38 but %s refused the request\n", other->name, active->name); + ast_debug(1, "starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", other->name, active->name); + + t38_parameters_fax_to_ast(control_params, &details->our_t38_parameters); + + if (fax_gateway_start(gateway, details, chan)) { + ast_log(LOG_ERROR, "error starting T.38 gateway for T.38 channel %s and G.711 channel %s\n", active->name, other->name); + gateway->t38_state = T38_STATE_REJECTED; + control_params->request_response = AST_T38_REFUSED; + + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + } else { + gateway->t38_state = T38_STATE_NEGOTIATED; + control_params->request_response = AST_T38_NEGOTIATED; + } + + ao2_ref(details, -1); + return f; + } else if (control_params->request_response == AST_T38_REQUEST_TERMINATE) { + /* the channel wishes to end our short relationship, we shall + * oblige */ + + ast_debug(1, "T.38 channel %s is requesting a shutdown of T.38, disabling the gateway\n", active->name); + + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + + gateway->t38_state = T38_STATE_REJECTED; + control_params->request_response = AST_T38_TERMINATED; + + fax_gateway_indicate_t38(chan, active, control_params); + + ao2_ref(details, -1); + return &ast_null_frame; + } else if (control_params->request_response == AST_T38_NEGOTIATED) { + ast_debug(1, "T.38 successfully negotiated between %s and %s, no gateway necessary\n", active->name, other->name); + + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + + ast_string_field_set(details, result, "SUCCESS"); + ast_string_field_set(details, resultstr, "no gateway necessary"); + ast_string_field_set(details, error, "NATIVE_T38"); + set_channel_variables(chan, details); + + ao2_ref(details, -1); + return f; + } else if (control_params->request_response == AST_T38_TERMINATED) { + ast_debug(1, "T.38 disabled on channel %s\n", active->name); + + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + + ao2_ref(details, -1); + return &ast_null_frame; + } + + ao2_ref(details, -1); + return f; +} + +/*! \brief Destroy the gateway data structure when the framehook is detached + * \param data framehook data (gateway data)*/ +static void fax_gateway_framehook_destroy(void *data) { + struct fax_gateway *gateway = data; + + if (gateway->s) { + switch (gateway->s->state) { + case AST_FAX_STATE_INITIALIZED: + case AST_FAX_STATE_OPEN: + case AST_FAX_STATE_ACTIVE: + case AST_FAX_STATE_COMPLETE: + if (gateway->s->tech->cancel_session) { + gateway->s->tech->cancel_session(gateway->s); + } + /* fall through */ + default: + break; + } + } + + ao2_ref(gateway, -1); +} + +/*! \brief T.30<->T.38 gateway framehook. + * + * Intercept packets on bridged channels and determine if a T.38 gateway is + * required. If a gateway is required, start a gateway and handle T.38 + * negotiation if necessary. + * + * \param chan channel running the gateway + * \param f frame to handle may be NULL + * \param event framehook event + * \param data framehook data (struct fax_gateway *) + * + * \return processed frame or NULL when f is NULL or a null frame + */ +static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data) { + struct fax_gateway *gateway = data; + struct ast_channel *peer, *active; + + /* restore audio formats when we are detached */ + if (event == AST_FRAMEHOOK_EVENT_DETACHED) { + set_channel_variables(chan, gateway->s->details); + + if (gateway->bridged) { + ast_set_read_format(chan, &gateway->chan_read_format); + ast_set_read_format(chan, &gateway->chan_write_format); + + if ((peer = ast_bridged_channel(chan))) { + ast_set_read_format(peer, &gateway->peer_read_format); + ast_set_read_format(peer, &gateway->peer_write_format); + ast_channel_make_compatible(chan, peer); + } + } + + return NULL; + } + + if (!f || (event == AST_FRAMEHOOK_EVENT_ATTACHED)) { + return NULL; + }; + + /* this frame was generated by the fax gateway, pass it on */ + if (ast_test_flag(f, AST_FAX_FRFLAG_GATEWAY)) { + return f; + } + + if (!(peer = ast_bridged_channel(chan))) { + /* not bridged, don't do anything */ + return f; + } + + if (!gateway->bridged && peer) { + /* don't start a gateway if neither channel can handle T.38 */ + if (ast_channel_get_t38_state(chan) == T38_STATE_UNAVAILABLE && ast_channel_get_t38_state(peer) == T38_STATE_UNAVAILABLE) { + ast_debug(1, "not starting gateway for %s and %s; neither channel supports T.38\n", chan->name, peer->name); + ast_framehook_detach(chan, gateway->framehook); + gateway->s->details->gateway_id = -1; + + ast_string_field_set(gateway->s->details, result, "FAILED"); + ast_string_field_set(gateway->s->details, resultstr, "neither channel supports T.38"); + ast_string_field_set(gateway->s->details, error, "T38_NEG_ERROR"); + set_channel_variables(chan, gateway->s->details); + return f; + } + + gateway->timeout_start = ast_tvnow(); + + /* we are bridged, change r/w formats to SLIN for CED detection and T.30 */ + ast_format_copy(&gateway->chan_read_format, &chan->readformat); + ast_format_copy(&gateway->chan_write_format, &chan->readformat); + + ast_format_copy(&gateway->peer_read_format, &peer->readformat); + ast_format_copy(&gateway->peer_write_format, &peer->readformat); + + ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR); + ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR); + + ast_set_read_format_by_id(peer, AST_FORMAT_SLINEAR); + ast_set_write_format_by_id(peer, AST_FORMAT_SLINEAR); + + ast_channel_make_compatible(chan, peer); + gateway->bridged = 1; + } + + if (gateway->bridged && !ast_tvzero(gateway->timeout_start)) { + if (ast_tvdiff_ms(ast_tvnow(), gateway->timeout_start) > FAX_GATEWAY_TIMEOUT) { + ast_debug(1, "no fax activity between %s and %s after %d ms, disabling gateway\n", chan->name, peer->name, FAX_GATEWAY_TIMEOUT); + ast_framehook_detach(chan, gateway->framehook); + gateway->s->details->gateway_id = -1; + + ast_string_field_set(gateway->s->details, result, "FAILED"); + ast_string_field_build(gateway->s->details, resultstr, "no fax activity after %d ms", FAX_GATEWAY_TIMEOUT); + ast_string_field_set(gateway->s->details, error, "TIMEOUT"); + set_channel_variables(chan, gateway->s->details); + return f; + } + } + + /* only handle VOICE, MODEM, and CONTROL frames*/ + switch (f->frametype) { + case AST_FRAME_VOICE: + switch (f->subclass.format.id) { + case AST_FORMAT_SLINEAR: + case AST_FORMAT_ALAW: + case AST_FORMAT_ULAW: + break; + default: + return f; + } + break; + case AST_FRAME_MODEM: + if (f->subclass.integer == AST_MODEM_T38) { + break; + } + return f; + case AST_FRAME_CONTROL: + if (f->subclass.integer == AST_CONTROL_T38_PARAMETERS) { + break; + } + return f; + default: + return f; + } + + /* detect the active channel */ + switch (event) { + case AST_FRAMEHOOK_EVENT_WRITE: + active = peer; + break; + case AST_FRAMEHOOK_EVENT_READ: + active = chan; + break; + default: + ast_log(LOG_WARNING, "unhandled framehook event %i\n", event); + return f; + } + + /* handle control frames */ + if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS) { + return fax_gateway_detect_t38(gateway, chan, peer, active, f); + } + + /* not in gateway mode yet, listen for CED */ + /* XXX this should detect a v21 preamble instead of CED */ + if (gateway->t38_state == T38_STATE_UNAVAILABLE && f->frametype == AST_FRAME_VOICE) { + return fax_gateway_detect_ced(gateway, chan, peer, active, f); + } + + /* in gateway mode, gateway some packets */ + if (gateway->t38_state == T38_STATE_NEGOTIATED) { + /* framehooks are called in __ast_read() before frame format + * translation is does, so we need to translate here */ + if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.format.id != AST_FORMAT_SLINEAR)) { + if (active->readtrans && (f = ast_translate(active->readtrans, f, 1)) == NULL) { + f = &ast_null_frame; + return f; + } + } + + /* XXX we ignore the return value here, perhaps we should + * disable the gateway if a write fails. I am not sure how a + * write would fail, or even if a failure would be fatal so for + * now we'll just ignore the return value. */ + gateway->s->tech->write(gateway->s, f); + f = &ast_null_frame; + return f; + } + + return f; +} + +/*! \brief Attach a gateway framehook object to a channel. + * \param chan the channel to attach to + * \param details fax session details + * \return the framehook id of the attached framehook or -1 on error + * \retval -1 error + */ +static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_details *details) +{ + struct fax_gateway *gateway; + struct ast_framehook_interface fr_hook = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = fax_gateway_framehook, + .destroy_cb = fax_gateway_framehook_destroy, + }; + + ast_string_field_set(details, result, "SUCCESS"); + ast_string_field_set(details, resultstr, "gateway operation started successfully"); + ast_string_field_set(details, error, "NO_ERROR"); + set_channel_variables(chan, details); + + /* set up the frame hook*/ + gateway = fax_gateway_new(details); + if (!gateway) { + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "error initializing gateway session"); + ast_string_field_set(details, error, "INIT_ERROR"); + set_channel_variables(chan, details); + report_fax_status(chan, details, "No Available Resource"); + return -1; + } + + fr_hook.data = gateway; + ast_channel_lock(chan); + gateway->framehook = ast_framehook_attach(chan, &fr_hook); + ast_channel_unlock(chan); + + if (gateway->framehook < 0) { + ao2_ref(gateway, -1); + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "error attaching gateway to channel"); + ast_string_field_set(details, error, "INIT_ERROR"); + set_channel_variables(chan, details); + return -1; + } + + return gateway->framehook; +} + /*! \brief hash callback for ao2 */ static int session_hash_cb(const void *obj, const int flags) { @@ -2501,19 +3263,15 @@ static char *cli_fax_show_sessions(struct ast_cli_entry *e, int cmd, struct ast_ while ((s = ao2_iterator_next(&i))) { ao2_lock(s); - if (!(filenames = generate_filenames_string(s->details, "", ", "))) { - ast_log(LOG_ERROR, "error printing filenames for 'fax show sessions' command"); - ao2_unlock(s); - ao2_ref(s, -1); - ao2_iterator_destroy(&i); - return CLI_FAILURE; - } + filenames = generate_filenames_string(s->details, "", ", "); ast_cli(a->fd, "%-20.20s %-10.10s %-10d %-5.5s %-10.10s %-15.15s %-30s\n", s->channame, s->tech->type, s->id, (s->details->caps & AST_FAX_TECH_AUDIO) ? "G.711" : "T.38", - (s->details->caps & AST_FAX_TECH_SEND) ? "send" : "receive", - ast_fax_state_to_str(s->state), filenames); + (s->details->caps & AST_FAX_TECH_GATEWAY) + ? "gateway" + : (s->details->caps & AST_FAX_TECH_SEND) ? "send" : "receive", + ast_fax_state_to_str(s->state), S_OR(filenames, "")); ast_free(filenames); ao2_unlock(s); @@ -2626,6 +3384,9 @@ static int acf_faxopt_read(struct ast_channel *chan, const char *cmd, char *data } if (!strcasecmp(data, "ecm")) { ast_copy_string(buf, details->option.ecm ? "yes" : "no", len); + } else if (!strcasecmp(data, "t38gateway") || !strcasecmp(data, "gateway") || + !strcasecmp(data, "t38_gateway") || !strcasecmp(data, "faxgateway")) { + ast_copy_string(buf, details->gateway_id != -1 ? "yes" : "no", len); } else if (!strcasecmp(data, "error")) { ast_copy_string(buf, details->error, len); } else if (!strcasecmp(data, "filename")) { @@ -2700,6 +3461,27 @@ static int acf_faxopt_write(struct ast_channel *chan, const char *cmd, char *dat } else { ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(ecm).\n", value); } + } else if (!strcasecmp(data, "t38gateway") || !strcasecmp(data, "gateway") || + !strcasecmp(data, "t38_gateway") || !strcasecmp(data, "faxgateway")) { + const char *val = ast_skip_blanks(value); + if (ast_true(val)) { + if (details->gateway_id < 0) { + details->gateway_id = fax_gateway_attach(chan, details); + if (details->gateway_id < 0) { + ast_log(LOG_ERROR, "Error attaching T.38 gateway to channel %s.\n", chan->name); + res = -1; + } else { + ast_debug(1, "Attached T.38 gateway to channel %s.\n", chan->name); + } + } else { + ast_log(LOG_WARNING, "Attempt to attach a T.38 gateway on channel (%s) with gateway already running.\n", chan->name); + } + } else if (ast_false(val)) { + ast_framehook_detach(chan, details->gateway_id); + details->gateway_id = -1; + } else { + ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(%s).\n", value, data); + } } else if (!strcasecmp(data, "headerinfo")) { ast_string_field_set(details, headerinfo, value); } else if (!strcasecmp(data, "localstationid")) { @@ -2789,6 +3571,7 @@ static int load_module(void) ao2_ref(faxregistry.container, -1); return AST_MODULE_LOAD_DECLINE; } + ast_cli_register_multiple(fax_cli, ARRAY_LEN(fax_cli)); res = ast_custom_function_register(&acf_faxopt); fax_logger_level = ast_logger_register_level("FAX"); diff --git a/res/res_fax_spandsp.c b/res/res_fax_spandsp.c index 851382b68..9ecd78305 100644 --- a/res/res_fax_spandsp.c +++ b/res/res_fax_spandsp.c @@ -5,6 +5,22 @@ * * Matthew Nicholson <mnicholson@digium.com> * + * Initial T.38-gateway code + * 2008, Daniel Ferenci <daniel.ferenci@nethemba.com> + * 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 <gregory@dnstelecom.co.za> + * dns Telecom http://www.dnstelecom.co.za + * + * Modified to make T.38-gateway compatible with Asterisk 1.6.2 + * 2010, Anton Verevkin <mymail@verevkin.it> + * ViaNetTV http://www.vianettv.com + * + * Modified to make T.38-gateway work + * 2010, Klaus Darilion, IPCom GmbH, www.ipcom.at + * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; @@ -46,9 +62,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/timing.h" #include "asterisk/astobj2.h" #include "asterisk/res_fax.h" +#include "asterisk/channel.h" #define SPANDSP_FAX_SAMPLES 160 #define SPANDSP_FAX_TIMER_RATE 8000 / SPANDSP_FAX_SAMPLES /* 50 ticks per second, 20ms, 160 samples per second */ +#define SPANDSP_ENGAGE_UDPTL_NAT_RETRY 3 static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_token *token); static void spandsp_fax_destroy(struct ast_fax_session *s); @@ -57,6 +75,9 @@ static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame * static int spandsp_fax_start(struct ast_fax_session *s); static int spandsp_fax_cancel(struct ast_fax_session *s); static int spandsp_fax_switch_to_t38(struct ast_fax_session *s); +static int spandsp_fax_gateway_start(struct ast_fax_session *s); +static int spandsp_fax_gateway_process(struct ast_fax_session *s, const struct ast_frame *f); +static void spandsp_fax_gateway_cleanup(struct ast_fax_session *s); static char *spandsp_fax_cli_show_capabilities(int fd); static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd); @@ -75,7 +96,7 @@ static struct ast_fax_tech spandsp_fax_tech = { */ .version = "pre-20090220", #endif - .caps = AST_FAX_TECH_AUDIO | AST_FAX_TECH_T38 | AST_FAX_TECH_SEND | AST_FAX_TECH_RECEIVE, + .caps = AST_FAX_TECH_AUDIO | AST_FAX_TECH_T38 | AST_FAX_TECH_SEND | AST_FAX_TECH_RECEIVE | AST_FAX_TECH_GATEWAY, .new_session = spandsp_fax_new, .destroy_session = spandsp_fax_destroy, .read = spandsp_fax_read, @@ -114,6 +135,7 @@ static struct { struct spandsp_pvt { unsigned int ist38:1; unsigned int isdone:1; + enum ast_t38_state ast_t38_state; fax_state_t fax_state; t38_terminal_state_t t38_state; t30_state_t *t30_state; @@ -121,6 +143,9 @@ struct spandsp_pvt { struct spandsp_fax_stats *stats; + struct spandsp_fax_gw_stats *t38stats; + t38_gateway_state_t t38_gw_state; + struct ast_timer *timer; AST_LIST_HEAD(frame_queue, ast_frame) read_frames; }; @@ -158,7 +183,9 @@ static void session_destroy(struct spandsp_pvt *p) */ static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, const uint8_t *buf, int len, int count) { - struct spandsp_pvt *p = data; + int res = -1; + struct ast_fax_session *s = data; + struct spandsp_pvt *p = s->tech_pvt; struct ast_frame fax_frame = { .frametype = AST_FRAME_MODEM, .subclass.integer = AST_MODEM_T38, @@ -174,13 +201,23 @@ static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, c AST_FRAME_SET_BUFFER(f, buf, 0, len); if (!(f = ast_frisolate(f))) { - return -1; + return res; } - /* no need to lock, this all runs in the same thread */ - AST_LIST_INSERT_TAIL(&p->read_frames, f, frame_list); + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + ast_set_flag(f, AST_FAX_FRFLAG_GATEWAY); + if (p->ast_t38_state == T38_STATE_NEGOTIATED) { + res = ast_write(s->chan, f); + } else { + res = ast_queue_frame(s->chan, f); + } + ast_frfree(f); + } else { + /* no need to lock, this all runs in the same thread */ + AST_LIST_INSERT_TAIL(&p->read_frames, f, frame_list); + } - return 0; + return res; } static int update_stats(struct spandsp_pvt *p, int completion_code) @@ -422,6 +459,11 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke goto e_return; } + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + s->state = AST_FAX_STATE_INITIALIZED; + return p; + } + AST_LIST_HEAD_INIT(&p->read_frames); if (s->details->caps & AST_FAX_TECH_RECEIVE) { @@ -450,7 +492,7 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke } /* init t38 stuff */ - t38_terminal_init(&p->t38_state, caller_mode, t38_tx_packet_handler, p); + t38_terminal_init(&p->t38_state, caller_mode, t38_tx_packet_handler, s); set_logging(&p->t38_state.logging, s->details); } @@ -475,7 +517,12 @@ static void spandsp_fax_destroy(struct ast_fax_session *s) { struct spandsp_pvt *p = s->tech_pvt; - session_destroy(p); + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + spandsp_fax_gateway_cleanup(s); + } else { + session_destroy(p); + } + ast_free(p); s->tech_pvt = NULL; s->fd = -1; @@ -536,6 +583,10 @@ static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame * { struct spandsp_pvt *p = s->tech_pvt; + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + return spandsp_fax_gateway_process(s, f); + } + /* XXX do we need to lock here? */ if (s->state == AST_FAX_STATE_COMPLETE) { ast_log(LOG_WARNING, "FAX session '%d' is in the '%s' state.\n", s->id, ast_fax_state_to_str(s->state)); @@ -549,6 +600,182 @@ static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame * } } +/*! \brief generate T.30 packets sent to the T.30 leg of gateway + * \param chan T.30 channel + * \param data fax session structure + * \param len not used + * \param samples no of samples generated + * \return -1 on failure or 0 on sucess*/ +static int spandsp_fax_gw_t30_gen(struct ast_channel *chan, void *data, int len, int samples) +{ + int res = -1; + struct ast_fax_session *s = data; + struct spandsp_pvt *p = s->tech_pvt; + uint8_t buffer[AST_FRIENDLY_OFFSET + samples * sizeof(uint16_t)]; + struct ast_frame *f; + struct ast_frame t30_frame = { + .frametype = AST_FRAME_VOICE, + .src = "res_fax_spandsp_g711", + .samples = samples, + .flags = AST_FAX_FRFLAG_GATEWAY, + }; + + AST_FRAME_SET_BUFFER(&t30_frame, buffer, AST_FRIENDLY_OFFSET, t30_frame.samples * sizeof(int16_t)); + + ast_format_set(&t30_frame.subclass.format, AST_FORMAT_SLINEAR, 0); + if (!(f = ast_frisolate(&t30_frame))) { + return p->isdone ? -1 : res; + } + + /* generate a T.30 packet */ + if ((f->samples = t38_gateway_tx(&p->t38_gw_state, f->data.ptr, f->samples))) { + f->datalen = f->samples * sizeof(int16_t); + res = ast_write(chan, f); + } + ast_frfree(f); + return p->isdone ? -1 : res; +} + +/*! \brief simple routine to allocate data to generator + * \param chan channel + * \param params generator data + * \return data to use in generator call*/ +static void *spandsp_fax_gw_gen_alloc(struct ast_channel *chan, void *params) { + ao2_ref(params, +1); + return params; +} + +static void spandsp_fax_gw_gen_release(struct ast_channel *chan, void *data) { + ao2_ref(data, -1); +} + +/*! \brief activate a spandsp gateway based on the information in the given fax session + * \param s fax session + * \return -1 on error 0 on sucess*/ +static int spandsp_fax_gateway_start(struct ast_fax_session *s) { + struct spandsp_pvt *p = s->tech_pvt; + struct ast_fax_t38_parameters *t38_param; + int i, modems = 0; + struct ast_channel *peer; + static struct ast_generator t30_gen = { + alloc: spandsp_fax_gw_gen_alloc, + release: spandsp_fax_gw_gen_release, + generate: spandsp_fax_gw_t30_gen, + }; + +#if SPANDSP_RELEASE_DATE >= 20081012 + /* for spandsp shaphots 0.0.6 and higher */ + p->t38_core_state=&p->t38_gw_state.t38x.t38; +#else + /* for spandsp release 0.0.5 */ + p->t38_core_state=&p->t38_gw_state.t38; +#endif + + if (!t38_gateway_init(&p->t38_gw_state, t38_tx_packet_handler, s)) { + return -1; + } + + p->ist38 = 1; + p->ast_t38_state = ast_channel_get_t38_state(s->chan); + if (!(peer = ast_bridged_channel(s->chan))) { + ast_channel_unlock(s->chan); + return -1; + } + ast_activate_generator(p->ast_t38_state == T38_STATE_NEGOTIATED ? peer : s->chan, &t30_gen , s); + + set_logging(&p->t38_gw_state.logging, s->details); + set_logging(&p->t38_core_state->logging, s->details); + + t38_param = (p->ast_t38_state == T38_STATE_NEGOTIATED) ? &s->details->our_t38_parameters : &s->details->their_t38_parameters; + t38_set_t38_version(p->t38_core_state, t38_param->version); + t38_gateway_set_ecm_capability(&p->t38_gw_state, s->details->option.ecm); + t38_set_max_datagram_size(p->t38_core_state, t38_param->max_ifp); + t38_set_fill_bit_removal(p->t38_core_state, t38_param->fill_bit_removal); + t38_set_mmr_transcoding(p->t38_core_state, t38_param->transcoding_mmr); + t38_set_jbig_transcoding(p->t38_core_state, t38_param->transcoding_jbig); + t38_set_data_rate_management_method(p->t38_core_state, + (t38_param->rate_management == AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF)? 1 : 2); + + t38_gateway_set_transmit_on_idle(&p->t38_gw_state, TRUE); + t38_set_sequence_number_handling(p->t38_core_state, TRUE); + + if (AST_FAX_MODEM_V17 & s->details->modems) { + modems |= T30_SUPPORT_V17; + } + if (AST_FAX_MODEM_V27 & s->details->modems) { + modems |= T30_SUPPORT_V27TER; + } + if (AST_FAX_MODEM_V29 & s->details->modems) { + modems |= T30_SUPPORT_V29; + } + if (AST_FAX_MODEM_V34 & s->details->modems) { +#if defined(T30_SUPPORT_V34) + modems |= T30_SUPPORT_V34; +#elif defined(T30_SUPPORT_V34HDX) + modems |= T30_SUPPORT_V34HDX; +#else + ast_log(LOG_WARNING, "v34 not supported in this version of spandsp\n"); +#endif + } + + t38_gateway_set_supported_modems(&p->t38_gw_state, modems); + + /* 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 < SPANDSP_ENGAGE_UDPTL_NAT_RETRY; i++) { +#if SPANDSP_RELEASE_DATE >= 20091228 + t38_core_send_indicator(&p->t38_gw_state.t38x.t38, T38_IND_NO_SIGNAL); +#elif SPANDSP_RELEASE_DATE >= 20081012 + t38_core_send_indicator(&p->t38_gw_state.t38x.t38, T38_IND_NO_SIGNAL, p->t38_gw_state.t38x.t38.indicator_tx_count); +#else + t38_core_send_indicator(&p->t38_gw_state.t38, T38_IND_NO_SIGNAL, p->t38_gw_state.t38.indicator_tx_count); +#endif + } + + s->state = AST_FAX_STATE_ACTIVE; + + return 0; +} + +/*! \brief process a frame from the bridge + * \param s fax session + * \param f frame to process + * \return 1 on sucess 0 on incorect packet*/ +static int spandsp_fax_gateway_process(struct ast_fax_session *s, const struct ast_frame *f) +{ + struct spandsp_pvt *p = s->tech_pvt; + + /*invalid frame*/ + if (!f->data.ptr || !f->datalen) { + return -1; + } + + /* Process a IFP packet */ + if ((f->frametype == AST_FRAME_MODEM) && (f->subclass.integer == AST_MODEM_T38)) { + return t38_core_rx_ifp_packet(p->t38_core_state, f->data.ptr, f->datalen, f->seqno); + } else if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.format.id == AST_FORMAT_SLINEAR)) { + return t38_gateway_rx(&p->t38_gw_state, f->data.ptr, f->samples); + } + + return -1; +} + +/*! \brief gather data and clean up after gateway ends + * \param s fax session*/ +static void spandsp_fax_gateway_cleanup(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + t38_stats_t t38_stats; + + t38_gateway_get_transfer_statistics(&p->t38_gw_state, &t38_stats); + + s->details->option.ecm = t38_stats.error_correcting_mode ? AST_FAX_OPTFLAG_TRUE : AST_FAX_OPTFLAG_FALSE; + s->details->pages_transferred = t38_stats.pages_transferred; + ast_string_field_build(s->details, transfer_rate, "%d", t38_stats.bit_rate); +} + /*! \brief */ static int spandsp_fax_start(struct ast_fax_session *s) { @@ -556,6 +783,10 @@ static int spandsp_fax_start(struct ast_fax_session *s) s->state = AST_FAX_STATE_OPEN; + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + return spandsp_fax_gateway_start(s); + } + if (p->ist38) { #if SPANDSP_RELEASE_DATE >= 20080725 /* for spandsp shaphots 0.0.6 and higher */ @@ -625,6 +856,12 @@ static int spandsp_fax_start(struct ast_fax_session *s) static int spandsp_fax_cancel(struct ast_fax_session *s) { struct spandsp_pvt *p = s->tech_pvt; + + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + p->isdone = 1; + return 0; + } + t30_terminate(p->t30_state); p->isdone = 1; return 0; @@ -653,7 +890,7 @@ static int spandsp_fax_switch_to_t38(struct ast_fax_session *s) /*! \brief */ static char *spandsp_fax_cli_show_capabilities(int fd) { - ast_cli(fd, "SEND RECEIVE T.38 G.711\n\n"); + ast_cli(fd, "SEND RECEIVE T.38 G.711 GATEWAY\n\n"); return CLI_SUCCESS; } @@ -661,35 +898,48 @@ static char *spandsp_fax_cli_show_capabilities(int fd) static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd) { struct spandsp_pvt *p = s->tech_pvt; - t30_stats_t stats; ao2_lock(s); - ast_cli(fd, "%-22s : %d\n", "session", s->id); - ast_cli(fd, "%-22s : %s\n", "operation", (s->details->caps & AST_FAX_TECH_RECEIVE) ? "Receive" : "Transmit"); - ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state)); - if (s->state != AST_FAX_STATE_UNINITIALIZED) { - t30_get_transfer_statistics(p->t30_state, &stats); - ast_cli(fd, "%-22s : %s\n", "Last Status", t30_completion_code_to_str(stats.current_status)); - ast_cli(fd, "%-22s : %s\n", "ECM Mode", stats.error_correcting_mode ? "Yes" : "No"); - ast_cli(fd, "%-22s : %d\n", "Data Rate", stats.bit_rate); - ast_cli(fd, "%-22s : %dx%d\n", "Image Resolution", stats.x_resolution, stats.y_resolution); + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + ast_cli(fd, "%-22s : %d\n", "session", s->id); + ast_cli(fd, "%-22s : %s\n", "operation", "Gateway"); + ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state)); + if (s->state != AST_FAX_STATE_UNINITIALIZED) { + t38_stats_t stats; + t38_gateway_get_transfer_statistics(&p->t38_gw_state, &stats); + ast_cli(fd, "%-22s : %s\n", "ECM Mode", stats.error_correcting_mode ? "Yes" : "No"); + ast_cli(fd, "%-22s : %d\n", "Data Rate", stats.bit_rate); + ast_cli(fd, "%-22s : %d\n", "Page Number", stats.pages_transferred + 1); + } + } else { + ast_cli(fd, "%-22s : %d\n", "session", s->id); + ast_cli(fd, "%-22s : %s\n", "operation", (s->details->caps & AST_FAX_TECH_RECEIVE) ? "Receive" : "Transmit"); + ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state)); + if (s->state != AST_FAX_STATE_UNINITIALIZED) { + t30_stats_t stats; + t30_get_transfer_statistics(p->t30_state, &stats); + ast_cli(fd, "%-22s : %s\n", "Last Status", t30_completion_code_to_str(stats.current_status)); + ast_cli(fd, "%-22s : %s\n", "ECM Mode", stats.error_correcting_mode ? "Yes" : "No"); + ast_cli(fd, "%-22s : %d\n", "Data Rate", stats.bit_rate); + ast_cli(fd, "%-22s : %dx%d\n", "Image Resolution", stats.x_resolution, stats.y_resolution); #if SPANDSP_RELEASE_DATE >= 20090220 - ast_cli(fd, "%-22s : %d\n", "Page Number", ((s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx) + 1); + ast_cli(fd, "%-22s : %d\n", "Page Number", ((s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx) + 1); #else - ast_cli(fd, "%-22s : %d\n", "Page Number", stats.pages_transferred + 1); + ast_cli(fd, "%-22s : %d\n", "Page Number", stats.pages_transferred + 1); #endif - ast_cli(fd, "%-22s : %s\n", "File Name", s->details->caps & AST_FAX_TECH_RECEIVE ? p->t30_state->rx_file : p->t30_state->tx_file); + ast_cli(fd, "%-22s : %s\n", "File Name", s->details->caps & AST_FAX_TECH_RECEIVE ? p->t30_state->rx_file : p->t30_state->tx_file); - ast_cli(fd, "\nData Statistics:\n"); + ast_cli(fd, "\nData Statistics:\n"); #if SPANDSP_RELEASE_DATE >= 20090220 - ast_cli(fd, "%-22s : %d\n", "Tx Pages", stats.pages_tx); - ast_cli(fd, "%-22s : %d\n", "Rx Pages", stats.pages_rx); + ast_cli(fd, "%-22s : %d\n", "Tx Pages", stats.pages_tx); + ast_cli(fd, "%-22s : %d\n", "Rx Pages", stats.pages_rx); #else - ast_cli(fd, "%-22s : %d\n", "Tx Pages", (s->details->caps & AST_FAX_TECH_SEND) ? stats.pages_transferred : 0); - ast_cli(fd, "%-22s : %d\n", "Rx Pages", (s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_transferred : 0); + ast_cli(fd, "%-22s : %d\n", "Tx Pages", (s->details->caps & AST_FAX_TECH_SEND) ? stats.pages_transferred : 0); + ast_cli(fd, "%-22s : %d\n", "Rx Pages", (s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_transferred : 0); #endif - ast_cli(fd, "%-22s : %d\n", "Longest Bad Line Run", stats.longest_bad_row_run); - ast_cli(fd, "%-22s : %d\n", "Total Bad Lines", stats.bad_rows); + ast_cli(fd, "%-22s : %d\n", "Longest Bad Line Run", stats.longest_bad_row_run); + ast_cli(fd, "%-22s : %d\n", "Total Bad Lines", stats.bad_rows); + } } ao2_unlock(s); ast_cli(fd, "\n\n"); |