diff --git a/CHANGES b/CHANGES index bff0630670e2cd8a272ab0f630b1022bddd4cb67..7fbc7a62284a93b21993552a6167dfdb98e2da85 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,14 @@ --- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------ ------------------------------------------------------------------------------ +bridge_t38 +------------------ + * A new module for the bridging framework, this bridge technology acts in the + same fashion as bridge_simple, save that it helps to maintain the state of + T.38 for two channels passing a fax through Asterisk. This helps to resolve + several esoteric issues that can occur when channels are removed from a + bridge after completing a T.38 fax. + ConfBridge ------------------ * A new "timeout" user profile option has been added. This configures the number diff --git a/bridges/bridge_t38.c b/bridges/bridge_t38.c new file mode 100644 index 0000000000000000000000000000000000000000..706ed227f316352c31da0371ee6c005bf8db7ba6 --- /dev/null +++ b/bridges/bridge_t38.c @@ -0,0 +1,261 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Digium, Inc. + * + * Matt Jordan <mjordan@digium.com> + * + * 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; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Bridging module for maintaining T.38 state for faxing channels + * + * \author Matt Jordan <mjordan@digium.com> + * + * \ingroup bridges + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/bridge.h" +#include "asterisk/bridge_technology.h" +#include "asterisk/frame.h" + +/*! \brief The current state of the T.38 fax for the channels in our bridge */ +struct t38_bridge_state { + /* \brief First channel in the bridge */ + struct ast_bridge_channel *bc0; + /*! \brief Second channel in the bridge */ + struct ast_bridge_channel *bc1; + /*! \brief T.38 state of \c bc0 */ + enum ast_t38_state c0_state; + /*! \brief T.38 state of \c bc1 */ + enum ast_t38_state c1_state; +}; + +static void t38_bridge_destroy(struct ast_bridge *bridge) +{ + struct t38_bridge_state *state = bridge->tech_pvt; + + ast_free(state); + bridge->tech_pvt = NULL; +} + +static int t38_bridge_create(struct ast_bridge *bridge) +{ + struct t38_bridge_state *state; + + state = ast_calloc(1, sizeof(*state)); + if (!state) { + return -1; + } + + bridge->tech_pvt = state; + + return 0; +} + +static int t38_bridge_start(struct ast_bridge *bridge) +{ + struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels); + struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels); + struct t38_bridge_state *state = bridge->tech_pvt; + + state->bc0 = bc0; + state->bc1 = bc1; + state->c0_state = ast_channel_get_t38_state(state->bc0->chan); + state->c1_state = ast_channel_get_t38_state(state->bc1->chan); + + return 0; +} + +static void send_termination_update(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, enum ast_t38_state chan_state) +{ + /* Inform the other side that T.38 faxing is done */ + struct ast_control_t38_parameters parameters = { .request_response = 0, }; + + if (!bridge_channel) { + return; + } + + ast_debug(5, "Bridge %s T.38: Current state of %s is %d\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), chan_state); + if (chan_state == T38_STATE_NEGOTIATING) { + parameters.request_response = AST_T38_REFUSED; + } else if (chan_state == T38_STATE_NEGOTIATED) { + parameters.request_response = AST_T38_TERMINATED; + } + + if (parameters.request_response) { + struct ast_frame f = { + .frametype = AST_FRAME_CONTROL, + .subclass.integer = AST_CONTROL_T38_PARAMETERS, + .data.ptr = ¶meters, + .datalen = sizeof(parameters), + }; + + /* When sending a termination update to a channel, the bridge is highly + * likely to be getting torn down. Queueing a frame through the bridging + * framework won't work, as the frame will likely just get tossed as the + * bridge collapses. Hence, we write directly to the channel to ensure that + * they know they aren't in a T.38 fax any longer. + */ + ast_debug(3, "Bridge %s T.38: Informing %s to switch to %d\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), parameters.request_response); + ast_write(bridge_channel->chan, &f); + } +} + +static void t38_bridge_stop(struct ast_bridge *bridge) +{ + struct t38_bridge_state *state = bridge->tech_pvt; + + send_termination_update(bridge, state->bc0, state->c0_state); + send_termination_update(bridge, state->bc1, state->c1_state); +} + +static void t38_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) +{ + struct t38_bridge_state *state = bridge->tech_pvt; + + if (bridge_channel == state->bc0) { + send_termination_update(bridge, state->bc0, state->c0_state); + state->bc0 = NULL; + state->c0_state = T38_STATE_UNKNOWN; + } else { + send_termination_update(bridge, state->bc1, state->c1_state); + state->bc1 = NULL; + state->c1_state = T38_STATE_UNKNOWN; + } +} + +static int t38_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +{ + struct t38_bridge_state *state = bridge->tech_pvt; + enum ast_t38_state *c_state; + enum ast_t38_state *other_state; + + if (!bridge_channel) { + return -1; + } + + c_state = bridge_channel == state->bc0 ? &(state->c0_state) : &(state->c1_state); + other_state = bridge_channel == state->bc0 ? &(state->c1_state) : &(state->c0_state); + + switch (frame->frametype) { + case AST_FRAME_CONTROL: + switch (frame->subclass.integer) { + case AST_CONTROL_T38_PARAMETERS: + { + struct ast_control_t38_parameters *parameters = frame->data.ptr; + + switch (parameters->request_response) { + case AST_T38_REQUEST_NEGOTIATE: + *c_state = T38_STATE_NEGOTIATING; + *other_state = T38_STATE_NEGOTIATING; + break; + case AST_T38_NEGOTIATED: + *c_state = T38_STATE_NEGOTIATED; + break; + case AST_T38_TERMINATED: + case AST_T38_REQUEST_TERMINATE: + case AST_T38_REFUSED: + *c_state = T38_STATE_REJECTED; + break; + case AST_T38_REQUEST_PARMS: + default: + /* No state change */ + break; + } + ast_debug(3, "Bridge %s T.38 state: %s: %d; %s: %d\n", + bridge->uniqueid, ast_channel_name(state->bc0->chan), state->c0_state, + ast_channel_name(state->bc1->chan), state->c1_state); + break; + } + default: + break; + } + break; + default: + break; + } + + return ast_bridge_queue_everyone_else(bridge, bridge_channel, frame); +} + +static int t38_bridge_compatible(struct ast_bridge *bridge) +{ + struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels); + struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels); + enum ast_t38_state c0_state; + enum ast_t38_state c1_state; + + /* We must have two, and only two, channels in a T.38 bridge */ + if (bridge->num_channels != 2) { + ast_debug(1, "Bridge '%s' can not use T.38 bridge as two channels are required\n", + bridge->uniqueid); + return 0; + } + + /* We can be the bridge tech so long as one side is in the process + * of negotiating T.38 + */ + c0_state = ast_channel_get_t38_state(bc0->chan); + c1_state = ast_channel_get_t38_state(bc1->chan); + if (c0_state != T38_STATE_NEGOTIATING && c0_state != T38_STATE_NEGOTIATED + && c1_state != T38_STATE_NEGOTIATING && c1_state != T38_STATE_NEGOTIATED) { + ast_debug(1, "Bridge '%s' can not use T.38 bridge: channel %s has T.38 state %d; channel %s has T.38 state %d\n", + bridge->uniqueid, ast_channel_name(bc0->chan), c0_state, ast_channel_name(bc1->chan), c1_state); + return 0; + } + + return 1; +} + +static struct ast_bridge_technology t38_bridge = { + .name = "t38_bridge", + .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX, + .preference = AST_BRIDGE_PREFERENCE_BASE_1TO1MIX + 1, + .create = t38_bridge_create, + .destroy = t38_bridge_destroy, + .start = t38_bridge_start, + .stop = t38_bridge_stop, + .leave = t38_bridge_leave, + .write = t38_bridge_write, + .compatible = t38_bridge_compatible, +}; + +static int unload_module(void) +{ + ast_bridge_technology_unregister(&t38_bridge); + + return 0; +} + +static int load_module(void) +{ + if (ast_bridge_technology_register(&t38_bridge)) { + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Two channel bridging module that maintains T.38 state");