Index: channels/chan_sip.c =================================================================== --- a/channels/chan_sip.c (revision 321680) +++ b/channels/chan_sip.c (working copy) @@ -4293,6 +4293,9 @@ case T38_ENABLED: state = T38_STATE_NEGOTIATED; break; + case T38_REJECTED: + state = T38_STATE_REJECTED; + break; default: state = T38_STATE_UNKNOWN; } @@ -4952,6 +4955,7 @@ parameters.request_response = AST_T38_NEGOTIATED; ast_udptl_set_tag(p->udptl, "SIP/%s", p->username); break; + case T38_REJECTED: case T38_DISABLED: if (old == T38_ENABLED) { parameters.request_response = AST_T38_TERMINATED; @@ -6510,11 +6514,11 @@ case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ /* Negotiation can not take place without a valid max_ifp value. */ if (!parameters->max_ifp) { - change_t38_state(p, T38_DISABLED); if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); } + change_t38_state(p, T38_REJECTED); break; } else if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); @@ -6552,7 +6556,7 @@ case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); - change_t38_state(p, T38_DISABLED); + change_t38_state(p, T38_REJECTED); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); } else if (p->t38.state == T38_ENABLED) transmit_reinvite_with_sdp(p, FALSE, FALSE); @@ -8979,7 +8983,7 @@ } } - if ((portno == -1) && (p->t38.state != T38_DISABLED)) { + if ((portno == -1) && (p->t38.state != T38_DISABLED) && (p->t38.state != T38_REJECTED)) { ast_debug(3, "Have T.38 but no audio, accepting offer anyway\n"); return 0; } @@ -18821,7 +18825,7 @@ } else if (!strcasecmp(data, "peername")) { ast_copy_string(buf, p->peername, len); } else if (!strcasecmp(data, "t38passthrough")) { - if (p->t38.state == T38_DISABLED) { + if ((p->t38.state == T38_DISABLED) || (p->t38.state == T38_REJECTED)) { ast_copy_string(buf, "0", len); } else { /* T38 is offered or enabled in this call */ ast_copy_string(buf, "1", len); @@ -19587,7 +19591,7 @@ case 606: /* Not Acceptable */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->udptl && p->t38.state == T38_LOCAL_REINVITE) { - change_t38_state(p, T38_DISABLED); + change_t38_state(p, T38_REJECTED); /* Try to reset RTP timers */ //ast_rtp_set_rtptimers_onhold(p->rtp); @@ -21313,7 +21317,7 @@ * want to abort the negotiation process */ if (p->t38id != -1) { - change_t38_state(p, T38_DISABLED); + change_t38_state(p, T38_REJECTED); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); p->t38id = -1; dialog_unref(p, "unref the dialog ptr from sip_t38_abort, because it held a dialog ptr"); @@ -22189,7 +22193,7 @@ } else if (p->t38.state == T38_ENABLED) { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL))); - } else if (p->t38.state == T38_DISABLED) { + } else if ((p->t38.state == T38_DISABLED) || (p->t38.state == T38_REJECTED)) { /* If this is not a re-invite or something to ignore - it's critical */ if (p->srtp && !ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)) { ast_log(LOG_WARNING, "Target does not support required crypto\n"); Index: channels/sip/include/sip.h =================================================================== --- a/channels/sip/include/sip.h (revision 321680) +++ b/channels/sip/include/sip.h (working copy) @@ -596,7 +596,8 @@ T38_DISABLED = 0, /*!< Not enabled */ T38_LOCAL_REINVITE, /*!< Offered from local - REINVITE */ T38_PEER_REINVITE, /*!< Offered from peer - REINVITE */ - T38_ENABLED /*!< Negotiated (enabled) */ + T38_ENABLED, /*!< Negotiated (enabled) */ + T38_REJECTED /*!< Refused */ }; /*! \brief Parameters to know status of transfer */ Index: channels/chan_local.c =================================================================== --- a/channels/chan_local.c (revision 321680) +++ b/channels/chan_local.c (working copy) @@ -588,6 +588,7 @@ static int local_write(struct ast_channel *ast, struct ast_frame *f) { struct local_pvt *p = ast->tech_pvt; + struct ast_channel *bridge; int res = -1; int isoutbound; @@ -596,13 +597,30 @@ } /* Just queue for delivery to the other side */ + while(ao2_trylock(p)) { + CHANNEL_DEADLOCK_AVOIDANCE(ast); + } ao2_ref(p, 1); /* ref for local_queue_frame */ ao2_lock(p); isoutbound = IS_OUTBOUND(ast, p); if (isoutbound && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) { check_bridge(ast, p); + } else if (!isoutbound) { + /* fixup formats nativeformat has changed we must adjust + * ast is p->owner and is locked here*/ + bridge = ast_bridged_channel(ast); + if (bridge && !(bridge->nativeformats & ast->nativeformats)) { + ast->nativeformats = bridge->nativeformats; + ast_set_read_format(ast, ast->readformat); + ast_set_write_format(ast, ast->writeformat); + ast_channel_lock(p->chan); + p->chan->nativeformats = bridge->nativeformats; + ast_set_read_format(p->chan, p->chan->readformat); + ast_set_write_format(p->chan, p->chan->writeformat); + ast_channel_unlock(p->chan); + } } if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) { Index: CHANGES =================================================================== --- a/CHANGES (revision 321680) +++ b/CHANGES (working copy) @@ -77,8 +77,8 @@ res_stun_monitor module support in chan_sip. * Addition of the 'auth_options_requests' option for turning on and off authentication for OPTIONS requests in chan_sip. + * Add T38 support for REJECTED state where T.38 Negotiation is explicitly rejected. - IAX2 Changes ----------- * Added rtsavesysname option into iax.conf to allow the systname to be saved @@ -513,6 +513,9 @@ applications will be lost, and that if the 'fax' logger level is directed to the console, the 'core set verbose' and 'core set debug' CLI commands will have no effect on whether the messages appear on the console or not. + * FAXOPT(faxgateway) will enable a framehook that will take care of T.38 + negotiation on reciving a CED tone on a channel. this gateway is to allow + translation of Audio T.30 [alaw/ulaw] to IFP T.38 terminals. Miscellaneous ------------- Index: addons/chan_ooh323.c =================================================================== --- a/addons/chan_ooh323.c (revision 321680) +++ b/addons/chan_ooh323.c (working copy) @@ -24,6 +24,42 @@ #include "chan_ooh323.h" #include +/*** DOCUMENTATION + + + Allow Setting / Reading OOH323 Settings + + + + + + Fax Detect [R/W] + Returns 0 or 1 + Write yes or no + + + + + t38support [R/W] + Returns 0 or 1 + Write yes or no + + + + + Returns h323id [R] + + + + + + Read and set channel parameters in the dialplan. + name is one of the above only those with a [W] can be writen to. + + + +***/ + #define FORMAT_STRING_SIZE 512 /* Defaults */ @@ -139,6 +175,8 @@ struct ast_rtp_instance *vrtp; /* Placeholder for now */ int t38support; /* T.38 mode - disable, transparent, faxgw */ + int faxdetect; + int faxdetected; int rtptimeout; struct ast_udptl *udptl; int faxmode; @@ -200,25 +238,26 @@ /* Profile of H.323 user registered with PBX*/ struct ooh323_user{ ast_mutex_t lock; - char name[256]; - char context[AST_MAX_EXTENSION]; - int incominglimit; - unsigned inUse; - char accountcode[20]; - int amaflags; - format_t capability; + char name[256]; + char context[AST_MAX_EXTENSION]; + int incominglimit; + unsigned inUse; + char accountcode[20]; + int amaflags; + format_t capability; struct ast_codec_pref prefs; - int dtmfmode; - int dtmfcodec; - int t38support; - int rtptimeout; - int mUseIP; /* Use IP address or H323-ID to search user */ - char mIP[20]; - struct OOH323Regex *rtpmask; - char rtpmaskstr[120]; - int rtdrcount, rtdrinterval; - int faststart, h245tunneling; - int g729onlyA; + int dtmfmode; + int dtmfcodec; + int faxdetect; + int t38support; + int rtptimeout; + int mUseIP; /* Use IP address or H323-ID to search user */ + char mIP[20]; + struct OOH323Regex *rtpmask; + char rtpmaskstr[120]; + int rtdrcount, rtdrinterval; + int faststart, h245tunneling; + int g729onlyA; struct ooh323_user *next; }; @@ -234,6 +273,7 @@ int amaflags; int dtmfmode; int dtmfcodec; + int faxdetect; int t38support; int mFriend; /* indicates defined as friend */ char ip[20]; @@ -294,6 +334,7 @@ static struct ast_codec_pref gPrefs; static int gDTMFMode = H323_DTMF_RFC2833; static int gDTMFCodec = 101; +static int gFAXdetect = 1; static int gT38Support = T38_FAXGW; static char gGatekeeper[100]; static enum RasGatekeeperMode gRasGkMode = RasNoGatekeeper; @@ -342,10 +383,12 @@ static struct ast_channel *ooh323_new(struct ooh323_pvt *i, int state, - const char *host, int capability, const char *linkedid) + const char *host, int capability, const char *linkedid) { struct ast_channel *ch = NULL; int fmt = 0; + int features = 0; + if (gH323Debug) ast_verbose("--- ooh323_new - %s, %d\n", host, capability); @@ -389,18 +432,29 @@ ast_module_ref(myself); /* Allocate dsp for in-band DTMF support */ - if (i->dtmfmode & H323_DTMF_INBAND) { + if ((i->dtmfmode & H323_DTMF_INBAND) || i->faxdetect) { i->vad = ast_dsp_new(); - ast_dsp_set_features(i->vad, DSP_FEATURE_DIGIT_DETECT); - ast_dsp_set_features(i->vad, - DSP_FEATURE_DIGIT_DETECT | DSP_FEATURE_FAX_DETECT); - ast_dsp_set_faxmode(i->vad, - DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED); + } - if (i->dtmfmode & H323_DTMF_INBANDRELAX) + /* inband DTMF*/ + if (i->dtmfmode & H323_DTMF_INBAND) { + features |= DSP_FEATURE_DIGIT_DETECT; + if (i->dtmfmode & H323_DTMF_INBANDRELAX) { ast_dsp_set_digitmode(i->vad, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); + } } + /* fax detection*/ + if (i->faxdetect) { + features |= DSP_FEATURE_FAX_DETECT; + ast_dsp_set_faxmode(i->vad, + DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED); + } + + if (features) { + ast_dsp_set_features(i->vad, features); + } + ast_mutex_lock(&usecnt_lock); usecnt++; ast_mutex_unlock(&usecnt_lock); @@ -516,6 +570,9 @@ ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_NONE); ast_udptl_set_far_max_datagram(pvt->udptl, 144); pvt->faxmode = 0; + pvt->chmodepend = 0; + pvt->faxdetected = 0; + pvt->faxdetect = gFAXdetect; pvt->t38support = gT38Support; pvt->rtptimeout = gRTPTimeout; pvt->rtdrinterval = gRTDRInterval; @@ -645,6 +702,7 @@ p->g729onlyA = peer->g729onlyA; p->dtmfmode |= peer->dtmfmode; p->dtmfcodec = peer->dtmfcodec; + p->faxdetect = peer->faxdetect; p->t38support = peer->t38support; p->rtptimeout = peer->rtptimeout; p->faststart = peer->faststart; @@ -674,6 +732,7 @@ p->g729onlyA = g729onlyA; p->dtmfmode = gDTMFMode; p->dtmfcodec = gDTMFCodec; + p->faxdetect = gFAXdetect; p->t38support = gT38Support; p->rtptimeout = gRTPTimeout; p->capability = gCapability; @@ -863,17 +922,7 @@ } ast_mutex_lock(&p->lock); - - if (digit == 'e' && !p->faxmode && p->t38support != T38_DISABLED) { - if (!p->chmodepend) { - if (gH323Debug) - ast_verbose("request to change %s to t.38 because fax cng\n", - p->callToken); - p->chmodepend = 1; - ooRequestChangeMode(p->callToken, 1); - } - - } else if (p->rtp && ((p->dtmfmode & H323_DTMF_RFC2833) || (p->dtmfmode & H323_DTMF_CISCO))) { + if (p->rtp && ((p->dtmfmode & H323_DTMF_RFC2833) || (p->dtmfmode & H323_DTMF_CISCO))) { ast_rtp_instance_dtmf_begin(p->rtp, digit); } else if (((p->dtmfmode & H323_DTMF_Q931) || (p->dtmfmode & H323_DTMF_H245ALPHANUMERIC) || @@ -1302,27 +1351,50 @@ (int)sizeof(enum ast_control_t38), (int)datalen); } else { const struct ast_control_t38_parameters *parameters = data; + struct ast_control_t38_parameters our_parameters; enum ast_control_t38 message = parameters->request_response; switch (message) { + case AST_T38_NEGOTIATED: + if (p->faxmode) { + res = 0; + break; + } case AST_T38_REQUEST_NEGOTIATE: - if (!p->chmodepend && !p->faxmode) { + if (p->faxmode) { + /* T.38 already negotiated */ + our_parameters.request_response = AST_T38_NEGOTIATED; + our_parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl); + our_parameters.rate = AST_T38_RATE_14400; + ast_queue_control_data(p->owner, AST_CONTROL_T38_PARAMETERS, &our_parameters, sizeof(our_parameters)); + } else if (!p->chmodepend) { + p->chmodepend = 1; ooRequestChangeMode(p->callToken, 1); - p->chmodepend = 1; res = 0; } break; case AST_T38_REQUEST_TERMINATE: - if (!p->chmodepend && p->faxmode) { + if (!p->faxmode) { + /* T.38 already terminated */ + our_parameters.request_response = AST_T38_TERMINATED; + ast_queue_control_data(p->owner, AST_CONTROL_T38_PARAMETERS, &our_parameters, sizeof(our_parameters)); + } else if (!p->chmodepend) { + p->chmodepend = 1; ooRequestChangeMode(p->callToken, 0); - p->chmodepend = 1; res = 0; } break; + case AST_T38_REQUEST_PARMS: + our_parameters.request_response = AST_T38_REQUEST_PARMS; + our_parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl); + our_parameters.rate = AST_T38_RATE_14400; + ast_queue_control_data(p->owner, AST_CONTROL_T38_PARAMETERS, &our_parameters, sizeof(our_parameters)); + res = AST_T38_REQUEST_PARMS; + break; default: ; @@ -1368,17 +1440,18 @@ case AST_OPTION_T38_STATE: if (*datalen != sizeof(enum ast_t38_state)) { - ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option." + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option." " Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen); break; } - if (p->t38support != T38_DISABLED) - state = T38_STATE_UNKNOWN; - if (p->faxmode) - state = (p->chmodepend) ? T38_STATE_UNKNOWN : T38_STATE_NEGOTIATED; - else if (p->chmodepend) - state = T38_STATE_NEGOTIATING; + if (p->t38support != T38_DISABLED) { + if (p->faxmode) { + state = (p->chmodepend) ? T38_STATE_NEGOTIATING : T38_STATE_NEGOTIATED; + } else { + state = T38_STATE_UNKNOWN; + } + } *((enum ast_t38_state *) data) = state; res = 0; @@ -1796,6 +1869,7 @@ memcpy(&p->prefs, &user->prefs, sizeof(struct ast_codec_pref)); p->dtmfmode |= user->dtmfmode; p->dtmfcodec = user->dtmfcodec; + p->faxdetect = user->faxdetect; p->t38support = user->t38support; p->rtptimeout = user->rtptimeout; p->h245tunneling = user->h245tunneling; @@ -2218,6 +2292,7 @@ user->rtptimeout = gRTPTimeout; user->dtmfmode = gDTMFMode; user->dtmfcodec = gDTMFCodec; + user->faxdetect = gFAXdetect; user->t38support = gT38Support; user->faststart = gFastStart; user->h245tunneling = gTunneling; @@ -2293,7 +2368,13 @@ user->dtmfmode |= ast_true(v->value) ? H323_DTMF_INBANDRELAX : 0; } else if (!strcasecmp(v->name, "dtmfcodec") && atoi(v->value)) { user->dtmfcodec = atoi(v->value); - } else if (!strcasecmp(v->name, "t38support")) { + } else if (!strcasecmp(v->name, "faxdetect")) { + if (ast_true(v->value)) { + user->faxdetect = 1; + } else { + user->faxdetect = 0; + } + } else if (!strcasecmp(v->name, "t38support")) { if (!strcasecmp(v->value, "disabled")) user->t38support = T38_DISABLED; if (!strcasecmp(v->value, "no")) @@ -2332,11 +2413,12 @@ peer->amaflags = gAMAFLAGS; peer->dtmfmode = gDTMFMode; peer->dtmfcodec = gDTMFCodec; + peer->faxdetect = gFAXdetect; peer->t38support = gT38Support; peer->faststart = gFastStart; peer->h245tunneling = gTunneling; peer->g729onlyA = g729onlyA; - peer->port = 1720; + peer->port = 1720; if (0 == friend_type) { peer->mFriend = 1; } @@ -2432,7 +2514,13 @@ peer->dtmfmode |= ast_true(v->value) ? H323_DTMF_INBANDRELAX : 0; } else if (!strcasecmp(v->name, "dtmfcodec") && atoi(v->value)) { peer->dtmfcodec = atoi(v->value); - } else if (!strcasecmp(v->name, "t38support")) { + } else if (!strcasecmp(v->name, "faxdetect")) { + if (ast_true(v->value)) { + peer->faxdetect = 1; + } else { + peer->faxdetect = 0; + } + } else if (!strcasecmp(v->name, "t38support")) { if (!strcasecmp(v->value, "disabled")) peer->t38support = T38_DISABLED; if (!strcasecmp(v->value, "no")) @@ -2553,6 +2641,7 @@ memset(&gPrefs, 0, sizeof(struct ast_codec_pref)); gDTMFMode = H323_DTMF_RFC2833; gDTMFCodec = 101; + gFAXdetect = 1; gT38Support = T38_FAXGW; gTRCLVL = OOTRCLVLERR; gRasGkMode = RasNoGatekeeper; @@ -2749,7 +2838,13 @@ gDTMFMode |= ast_true(v->value) ? H323_DTMF_INBANDRELAX : 0; } else if (!strcasecmp(v->name, "dtmfcodec") && atoi(v->value)) { gDTMFCodec = atoi(v->value); - } else if (!strcasecmp(v->name, "t38support")) { + } else if (!strcasecmp(v->name, "faxdetect")) { + if (ast_true(v->value)) { + gFAXdetect = 1; + } else { + gFAXdetect = 0; + } + } else if (!strcasecmp(v->name, "t38support")) { if (!strcasecmp(v->value, "disabled")) gT38Support = T38_DISABLED; if (!strcasecmp(v->value, "no")) @@ -2836,14 +2931,13 @@ if (a->argc != 4) return CLI_SHOWUSAGE; - ast_mutex_lock(&peerl.lock); peer = peerl.peers; while (peer) { ast_mutex_lock(&peer->lock); - if(!strcmp(peer->name, a->argv[3])) + if (!strcmp(peer->name, a->argv[3])) { break; - else { + } else { prev = peer; peer = peer->next; ast_mutex_unlock(&prev->lock); @@ -2851,53 +2945,55 @@ } if (peer) { - sprintf(ip_port, "%s:%d", peer->ip, peer->port); - ast_cli(a->fd, "%-15.15s%s\n", "Name: ", peer->name); - ast_cli(a->fd, "%s:%s,%s\n", "FastStart/H.245 Tunneling", peer->faststart?"yes":"no", + sprintf(ip_port, "%s:%d", peer->ip, peer->port); + ast_cli(a->fd, "%-15.15s%s\n", "Name: ", peer->name); + ast_cli(a->fd, "%s:%s,%s\n", "FastStart/H.245 Tunneling", peer->faststart?"yes":"no", peer->h245tunneling?"yes":"no"); - ast_cli(a->fd, "%-15.15s%s", "Format Prefs: ", "("); - print_codec_to_cli(a->fd, &peer->prefs); - ast_cli(a->fd, ")\n"); - ast_cli(a->fd, "%-15.15s", "DTMF Mode: "); + ast_cli(a->fd, "%-15.15s%s", "Format Prefs: ", "("); + print_codec_to_cli(a->fd, &peer->prefs); + ast_cli(a->fd, ")\n"); + ast_cli(a->fd, "%-15.15s", "DTMF Mode: "); if (peer->dtmfmode & H323_DTMF_CISCO) { - ast_cli(a->fd, "%s\n", "cisco"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", peer->dtmfcodec); + ast_cli(a->fd, "%s\n", "cisco"); + ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", peer->dtmfcodec); } else if (peer->dtmfmode & H323_DTMF_RFC2833) { - ast_cli(a->fd, "%s\n", "rfc2833"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", peer->dtmfcodec); - } else if (peer->dtmfmode & H323_DTMF_Q931) - ast_cli(a->fd, "%s\n", "q931keypad"); - else if (peer->dtmfmode & H323_DTMF_H245ALPHANUMERIC) - ast_cli(a->fd, "%s\n", "h245alphanumeric"); - else if (peer->dtmfmode & H323_DTMF_H245SIGNAL) - ast_cli(a->fd, "%s\n", "h245signal"); - else if (peer->dtmfmode & H323_DTMF_INBAND && peer->dtmfmode & H323_DTMF_INBANDRELAX) - ast_cli(a->fd, "%s\n", "inband-relaxed"); - else if (peer->dtmfmode & H323_DTMF_INBAND) - ast_cli(a->fd, "%s\n", "inband"); - else - ast_cli(a->fd, "%s\n", "unknown"); - - ast_cli(a->fd,"%-15s", "T.38 Mode: "); - if (peer->t38support == T38_DISABLED) - ast_cli(a->fd, "%s\n", "disabled"); - else if (peer->t38support == T38_FAXGW) - ast_cli(a->fd, "%s\n", "faxgw/chan_sip compatible"); - - ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", peer->accountcode); - ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", - ast_cdr_flags2str(peer->amaflags)); - ast_cli(a->fd, "%-15.15s%s\n", "IP:Port: ", ip_port); - ast_cli(a->fd, "%-15.15s%d\n", "OutgoingLimit: ", peer->outgoinglimit); - ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", peer->rtptimeout); - if (peer->rtpmaskstr[0]) - ast_cli(a->fd, "%-15.15s%s\n", "rtpmask: ", peer->rtpmaskstr); - if (peer->rtdrcount && peer->rtdrinterval) - ast_cli(a->fd, "%-15.15s%d,%d\n", "RoundTrip: ", peer->rtdrcount, peer->rtdrinterval); - ast_mutex_unlock(&peer->lock); + ast_cli(a->fd, "%s\n", "rfc2833"); + ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", peer->dtmfcodec); + } else if (peer->dtmfmode & H323_DTMF_Q931) { + ast_cli(a->fd, "%s\n", "q931keypad"); + } else if (peer->dtmfmode & H323_DTMF_H245ALPHANUMERIC) { + ast_cli(a->fd, "%s\n", "h245alphanumeric"); + } else if (peer->dtmfmode & H323_DTMF_H245SIGNAL) { + ast_cli(a->fd, "%s\n", "h245signal"); + } else if (peer->dtmfmode & H323_DTMF_INBAND && peer->dtmfmode & H323_DTMF_INBANDRELAX) { + ast_cli(a->fd, "%s\n", "inband-relaxed"); + } else if (peer->dtmfmode & H323_DTMF_INBAND) { + ast_cli(a->fd, "%s\n", "inband"); + } else { + ast_cli(a->fd, "%s\n", "unknown"); + } + ast_cli(a->fd,"%-15s", "T.38 Mode: "); + if (peer->t38support == T38_DISABLED) { + ast_cli(a->fd, "%s\n", "disabled"); + } else if (peer->t38support == T38_FAXGW) { + ast_cli(a->fd, "%s\n", "faxgw/chan_sip compatible"); + } + ast_cli(a->fd,"%-15s%s\n", "FAX Detect:", (peer->faxdetect) ? "Yes" : "No"); + ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", peer->accountcode); + ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(peer->amaflags)); + ast_cli(a->fd, "%-15.15s%s\n", "IP:Port: ", ip_port); + ast_cli(a->fd, "%-15.15s%d\n", "OutgoingLimit: ", peer->outgoinglimit); + ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", peer->rtptimeout); + if (peer->rtpmaskstr[0]) { + ast_cli(a->fd, "%-15.15s%s\n", "rtpmask: ", peer->rtpmaskstr); + } + if (peer->rtdrcount && peer->rtdrinterval) { + ast_cli(a->fd, "%-15.15s%d,%d\n", "RoundTrip: ", peer->rtdrcount, peer->rtdrinterval); + } + ast_mutex_unlock(&peer->lock); } else { - ast_cli(a->fd, "Peer %s not found\n", a->argv[3]); - ast_cli(a->fd, "\n"); + ast_cli(a->fd, "Peer %s not found\n", a->argv[3]); + ast_cli(a->fd, "\n"); } ast_mutex_unlock(&peerl.lock); @@ -2987,9 +3083,9 @@ user = userl.users; while (user) { ast_mutex_lock(&user->lock); - if(!strcmp(user->name, a->argv[3])) { + if (!strcmp(user->name, a->argv[3])) { break; - } else { + } else { prev = user; user = user->next; ast_mutex_unlock(&prev->lock); @@ -2997,53 +3093,55 @@ } if (user) { - ast_cli(a->fd, "%-15.15s%s\n", "Name: ", user->name); - ast_cli(a->fd, "%s:%s,%s\n", "FastStart/H.245 Tunneling", user->faststart?"yes":"no", + ast_cli(a->fd, "%-15.15s%s\n", "Name: ", user->name); + ast_cli(a->fd, "%s:%s,%s\n", "FastStart/H.245 Tunneling", user->faststart?"yes":"no", user->h245tunneling?"yes":"no"); - ast_cli(a->fd, "%-15.15s%s", "Format Prefs: ", "("); - print_codec_to_cli(a->fd, &user->prefs); - ast_cli(a->fd, ")\n"); - ast_cli(a->fd, "%-15.15s", "DTMF Mode: "); + ast_cli(a->fd, "%-15.15s%s", "Format Prefs: ", "("); + print_codec_to_cli(a->fd, &user->prefs); + ast_cli(a->fd, ")\n"); + ast_cli(a->fd, "%-15.15s", "DTMF Mode: "); if (user->dtmfmode & H323_DTMF_CISCO) { - ast_cli(a->fd, "%s\n", "cisco"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", user->dtmfcodec); + ast_cli(a->fd, "%s\n", "cisco"); + ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", user->dtmfcodec); } else if (user->dtmfmode & H323_DTMF_RFC2833) { - ast_cli(a->fd, "%s\n", "rfc2833"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", user->dtmfcodec); - } else if (user->dtmfmode & H323_DTMF_Q931) - ast_cli(a->fd, "%s\n", "q931keypad"); - else if (user->dtmfmode & H323_DTMF_H245ALPHANUMERIC) - ast_cli(a->fd, "%s\n", "h245alphanumeric"); - else if (user->dtmfmode & H323_DTMF_H245SIGNAL) - ast_cli(a->fd, "%s\n", "h245signal"); - else if (user->dtmfmode & H323_DTMF_INBAND && user->dtmfmode & H323_DTMF_INBANDRELAX) - ast_cli(a->fd, "%s\n", "inband-relaxed"); - else if (user->dtmfmode & H323_DTMF_INBAND) - ast_cli(a->fd, "%s\n", "inband"); - else - ast_cli(a->fd, "%s\n", "unknown"); - - ast_cli(a->fd,"%-15s", "T.38 Mode: "); - if (user->t38support == T38_DISABLED) - ast_cli(a->fd, "%s\n", "disabled"); - else if (user->t38support == T38_FAXGW) - ast_cli(a->fd, "%s\n", "faxgw/chan_sip compatible"); - - ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", user->accountcode); - ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", - ast_cdr_flags2str(user->amaflags)); - ast_cli(a->fd, "%-15.15s%s\n", "Context: ", user->context); - ast_cli(a->fd, "%-15.15s%d\n", "IncomingLimit: ", user->incominglimit); - ast_cli(a->fd, "%-15.15s%d\n", "InUse: ", user->inUse); - ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", user->rtptimeout); - if (user->rtpmaskstr[0]) - ast_cli(a->fd, "%-15.15s%s\n", "rtpmask: ", user->rtpmaskstr); + ast_cli(a->fd, "%s\n", "rfc2833"); + ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", user->dtmfcodec); + } else if (user->dtmfmode & H323_DTMF_Q931) { + ast_cli(a->fd, "%s\n", "q931keypad"); + } else if (user->dtmfmode & H323_DTMF_H245ALPHANUMERIC) { + ast_cli(a->fd, "%s\n", "h245alphanumeric"); + } else if (user->dtmfmode & H323_DTMF_H245SIGNAL) { + ast_cli(a->fd, "%s\n", "h245signal"); + } else if (user->dtmfmode & H323_DTMF_INBAND && user->dtmfmode & H323_DTMF_INBANDRELAX) { + ast_cli(a->fd, "%s\n", "inband-relaxed"); + } else if (user->dtmfmode & H323_DTMF_INBAND) { + ast_cli(a->fd, "%s\n", "inband"); + } else { + ast_cli(a->fd, "%s\n", "unknown"); + } + ast_cli(a->fd,"%-15s", "T.38 Mode: "); + if (user->t38support == T38_DISABLED) { + ast_cli(a->fd, "%s\n", "disabled"); + } else if (user->t38support == T38_FAXGW) { + ast_cli(a->fd, "%s\n", "faxgw/chan_sip compatible"); + } + ast_cli(a->fd,"%-15s%s\n", "FAX Detect:", (user->faxdetect) ? "Yes" : "No"); + ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", user->accountcode); + ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(user->amaflags)); + ast_cli(a->fd, "%-15.15s%s\n", "Context: ", user->context); + ast_cli(a->fd, "%-15.15s%d\n", "IncomingLimit: ", user->incominglimit); + ast_cli(a->fd, "%-15.15s%d\n", "InUse: ", user->inUse); + ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", user->rtptimeout); + if (user->rtpmaskstr[0]) { + ast_cli(a->fd, "%-15.15s%s\n", "rtpmask: ", user->rtpmaskstr); + } ast_mutex_unlock(&user->lock); - if (user->rtdrcount && user->rtdrinterval) - ast_cli(a->fd, "%-15.15s%d,%d\n", "RoundTrip: ", user->rtdrcount, user->rtdrinterval); + if (user->rtdrcount && user->rtdrinterval) { + ast_cli(a->fd, "%-15.15s%d,%d\n", "RoundTrip: ", user->rtdrcount, user->rtdrinterval); + } } else { - ast_cli(a->fd, "User %s not found\n", a->argv[3]); - ast_cli(a->fd, "\n"); + ast_cli(a->fd, "User %s not found\n", a->argv[3]); + ast_cli(a->fd, "\n"); } ast_mutex_unlock(&userl.lock); @@ -3142,91 +3240,85 @@ if (a->argc != 3) return CLI_SHOWUSAGE; - - - ast_cli(a->fd, "\nObjective Open H.323 Channel Driver's Config:\n"); + ast_cli(a->fd, "\nObjective Open H.323 Channel Driver's Config:\n"); snprintf(value, sizeof(value), "%s:%d", gIP, gPort); - ast_cli(a->fd, "%-20s%s\n", "IP:Port: ", value); - ast_cli(a->fd, "%-20s%d-%d\n", "H.225 port range: ", - ooconfig.mTCPPortStart, ooconfig.mTCPPortEnd); - ast_cli(a->fd, "%-20s%s\n", "FastStart", gFastStart?"yes":"no"); - ast_cli(a->fd, "%-20s%s\n", "Tunneling", gTunneling?"yes":"no"); - ast_cli(a->fd, "%-20s%s\n", "CallerId", gCallerID); - ast_cli(a->fd, "%-20s%s\n", "MediaWaitForConnect", - gMediaWaitForConnect?"yes":"no"); + ast_cli(a->fd, "%-20s%s\n", "IP:Port: ", value); + ast_cli(a->fd, "%-20s%d-%d\n", "H.225 port range: ", ooconfig.mTCPPortStart, ooconfig.mTCPPortEnd); + ast_cli(a->fd, "%-20s%s\n", "FastStart", gFastStart?"yes":"no"); + ast_cli(a->fd, "%-20s%s\n", "Tunneling", gTunneling?"yes":"no"); + ast_cli(a->fd, "%-20s%s\n", "CallerId", gCallerID); + ast_cli(a->fd, "%-20s%s\n", "MediaWaitForConnect", gMediaWaitForConnect?"yes":"no"); #if (0) extern OOH323EndPoint gH323ep; - ast_cli(a->fd, "%-20s%s\n", "FASTSTART", - (OO_TESTFLAG(gH323ep.flags, OO_M_FASTSTART) != 0) ? "yes" : "no"); - ast_cli(a->fd, "%-20s%s\n", "TUNNELING", - (OO_TESTFLAG(gH323ep.flags, OO_M_TUNNELING) != 0) ? "yes" : "no"); - ast_cli(a->fd, "%-20s%s\n", "MEDIAWAITFORCONN", - (OO_TESTFLAG(gH323ep.flags, OO_M_MEDIAWAITFORCONN) != 0) ? "yes" : "no"); + ast_cli(a->fd, "%-20s%s\n", "FASTSTART", + (OO_TESTFLAG(gH323ep.flags, OO_M_FASTSTART) != 0) ? "yes" : "no"); + ast_cli(a->fd, "%-20s%s\n", "TUNNELING", + (OO_TESTFLAG(gH323ep.flags, OO_M_TUNNELING) != 0) ? "yes" : "no"); + ast_cli(a->fd, "%-20s%s\n", "MEDIAWAITFORCONN", + (OO_TESTFLAG(gH323ep.flags, OO_M_MEDIAWAITFORCONN) != 0) ? "yes" : "no"); #endif - if (gRasGkMode == RasNoGatekeeper) + if (gRasGkMode == RasNoGatekeeper) { snprintf(value, sizeof(value), "%s", "No Gatekeeper"); - else if (gRasGkMode == RasDiscoverGatekeeper) + } else if (gRasGkMode == RasDiscoverGatekeeper) { snprintf(value, sizeof(value), "%s", "Discover"); - else + } else { snprintf(value, sizeof(value), "%s", gGatekeeper); - - ast_cli(a->fd, "%-20s%s\n", "Gatekeeper:", value); - - ast_cli(a->fd, "%-20s%s\n", "H.323 LogFile:", gLogFile); - - ast_cli(a->fd, "%-20s%s\n", "Context:", gContext); - - ast_cli(a->fd, "%-20s%s\n", "Capability:", - ast_getformatname_multiple(value,FORMAT_STRING_SIZE,gCapability)); - - ast_cli(a->fd, "%-20s", "DTMF Mode: "); + } + ast_cli(a->fd, "%-20s%s\n", "Gatekeeper:", value); + ast_cli(a->fd, "%-20s%s\n", "H.323 LogFile:", gLogFile); + ast_cli(a->fd, "%-20s%s\n", "Context:", gContext); + ast_cli(a->fd, "%-20s%s\n", "Capability:", + ast_getformatname_multiple(value,FORMAT_STRING_SIZE,gCapability)); + ast_cli(a->fd, "%-20s", "DTMF Mode: "); if (gDTMFMode & H323_DTMF_CISCO) { - ast_cli(a->fd, "%s\n", "cisco"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", gDTMFCodec); + ast_cli(a->fd, "%s\n", "cisco"); + ast_cli(a->fd, "%-20.15s%d\n", "DTMF Codec: ", gDTMFCodec); } else if (gDTMFMode & H323_DTMF_RFC2833) { - ast_cli(a->fd, "%s\n", "rfc2833"); - ast_cli(a->fd, "%-15.15s%d\n", "DTMF Codec: ", gDTMFCodec); - } else if (gDTMFMode & H323_DTMF_Q931) - ast_cli(a->fd, "%s\n", "q931keypad"); - else if (gDTMFMode & H323_DTMF_H245ALPHANUMERIC) - ast_cli(a->fd, "%s\n", "h245alphanumeric"); - else if (gDTMFMode & H323_DTMF_H245SIGNAL) - ast_cli(a->fd, "%s\n", "h245signal"); - else if (gDTMFMode & H323_DTMF_INBAND && gDTMFMode & H323_DTMF_INBANDRELAX) - ast_cli(a->fd, "%s\n", "inband-relaxed"); - else if (gDTMFMode & H323_DTMF_INBAND) - ast_cli(a->fd, "%s\n", "inband"); - else + ast_cli(a->fd, "%s\n", "rfc2833"); + ast_cli(a->fd, "%-20.15s%d\n", "DTMF Codec: ", gDTMFCodec); + } else if (gDTMFMode & H323_DTMF_Q931) { + ast_cli(a->fd, "%s\n", "q931keypad"); + } else if (gDTMFMode & H323_DTMF_H245ALPHANUMERIC) { + ast_cli(a->fd, "%s\n", "h245alphanumeric"); + } else if (gDTMFMode & H323_DTMF_H245SIGNAL) { + ast_cli(a->fd, "%s\n", "h245signal"); + } else if (gDTMFMode & H323_DTMF_INBAND && gDTMFMode & H323_DTMF_INBANDRELAX) { + ast_cli(a->fd, "%s\n", "inband-relaxed"); + } else if (gDTMFMode & H323_DTMF_INBAND) { + ast_cli(a->fd, "%s\n", "inband"); + } else { ast_cli(a->fd, "%s\n", "unknown"); + } - ast_cli(a->fd,"%-20s", "T.38 Mode: "); - if (gT38Support == T38_DISABLED) + ast_cli(a->fd,"%-20s", "T.38 Mode: "); + if (gT38Support == T38_DISABLED) { ast_cli(a->fd, "%s\n", "disabled"); - else if (gT38Support == T38_FAXGW) + } else if (gT38Support == T38_FAXGW) { ast_cli(a->fd, "%s\n", "faxgw/chan_sip compatible"); + } + ast_cli(a->fd,"%-20s%s\n", "FAX Detect:", (gFAXdetect) ? "Yes" : "No"); - if (gRTDRCount && gRTDRInterval) - ast_cli(a->fd, "%-15.15s%d,%d\n", "RoundTrip: ", gRTDRCount, gRTDRInterval); + if (gRTDRCount && gRTDRInterval) { + ast_cli(a->fd, "%-20.15s%d,%d\n", "RoundTrip: ", gRTDRCount, gRTDRInterval); + } - ast_cli(a->fd, "%-20s%ld\n", "Call counter: ", callnumber); - ast_cli(a->fd, "%-20s%s\n", "AccountCode: ", gAccountcode); + ast_cli(a->fd, "%-20s%ld\n", "Call counter: ", callnumber); + ast_cli(a->fd, "%-20s%s\n", "AccountCode: ", gAccountcode); + ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_cdr_flags2str(gAMAFLAGS)); - ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_cdr_flags2str(gAMAFLAGS)); - pAlias = gAliasList; - if(pAlias) { - ast_cli(a->fd, "%-20s\n", "Aliases: "); - } + if(pAlias) { + ast_cli(a->fd, "%-20s\n", "Aliases: "); + } while (pAlias) { pAliasNext = pAlias->next; if (pAliasNext) { - ast_cli(a->fd,"\t%-30s\t%-30s\n",pAlias->value, pAliasNext->value); + ast_cli(a->fd,"\t%-30s\t%-30s\n",pAlias->value, pAliasNext->value); pAlias = pAliasNext->next; - } - else{ - ast_cli(a->fd,"\t%-30s\n",pAlias->value); + } else { + ast_cli(a->fd,"\t%-30s\n",pAlias->value); pAlias = pAlias->next; } } @@ -3243,7 +3335,81 @@ AST_CLI_DEFINE(handle_cli_ooh323_reload, "reload ooh323 config") }; +/*! \brief OOH323 Dialplan function - reads ooh323 settings */ +static int function_ooh323_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct ooh323_pvt *p = chan->tech_pvt; + if (strcmp(chan->tech->type, "OOH323")) { + ast_log(LOG_ERROR, "This function is only supported on OOH323 channels Channel is %s\n", chan->tech->type); + return -1; + } + + if (!strcasecmp(data, "faxdetect")) { + ast_copy_string(buf, p->faxdetect ? "1" : "0", len); + } else if (!strcasecmp(data, "t38support")) { + ast_copy_string(buf, p->t38support ? "1" : "0", len); + } else if (!strcasecmp(data, "caller_h323id")) { + ast_copy_string(buf, p->caller_h323id, len); + } else if (!strcasecmp(data, "caller_dialeddigits")) { + ast_copy_string(buf, p->caller_dialedDigits, len); + } else if (!strcasecmp(data, "caller_email")) { + ast_copy_string(buf, p->caller_email, len); + } else if (!strcasecmp(data, "h323id_url")) { + ast_copy_string(buf, p->caller_url, len); + } else if (!strcasecmp(data, "callee_h323id")) { + ast_copy_string(buf, p->callee_h323id, len); + } else if (!strcasecmp(data, "callee_dialeddigits")) { + ast_copy_string(buf, p->callee_dialedDigits, len); + } else if (!strcasecmp(data, "callee_email")) { + ast_copy_string(buf, p->callee_email, len); + } else if (!strcasecmp(data, "callee_url")) { + ast_copy_string(buf, p->callee_url, len); + } + return 0; +} + +/*! \brief OOH323 Dialplan function - writes ooh323 settings */ +static int function_ooh323_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct ooh323_pvt *p = chan->tech_pvt; + + int res = -1; + if (strcmp(chan->tech->type, "OOH323")) { + ast_log(LOG_ERROR, "This function is only supported on OOH323 channels Channel is %s\n", chan->tech->type); + return res; + } + + ast_mutex_lock(&p->lock); + if (!strcasecmp(data, "faxdetect")) { + if (ast_true(value)) { + p->faxdetect = 1; + res = 0; + } else { + p->faxdetect = 0; + res = 0; + } + } else if (!strcasecmp(data, "t38support")) { + if (ast_true(value)) { + p->t38support = 1; + res = 0; + } else { + p->t38support = 0; + res = 0; + } + } + ast_mutex_unlock(&p->lock); + + return res; +} + +/*! \brief Structure to declare a dialplan function: OOH323 */ +static struct ast_custom_function ooh323_function = { + .name = "OOH323", + .read = function_ooh323_read, + .write = function_ooh323_write, +}; + static int load_module(void) { int res; @@ -3400,6 +3566,9 @@ restart_monitor(); } + /* Register dialplan functions */ + ast_custom_function_register(&ooh323_function); + return 0; } @@ -3674,7 +3843,7 @@ ast_mutex_unlock(&userl.lock); return 0; } - + static int unload_module(void) { struct ooh323_pvt *p; @@ -3789,8 +3958,11 @@ } ooH323EpDestroy(); + /* Unregister dial plan functions */ + ast_custom_function_unregister(&ooh323_function); + if (gH323Debug) { - ast_verbose("+++ ooh323 unload_module \n"); + ast_verbose("+++ ooh323 unload_module \n"); } return 0; @@ -4255,6 +4427,7 @@ { /* Retrieve audio/etc from channel. Assumes p->lock is already held. */ struct ast_frame *f; + struct ast_frame *dfr = NULL; static struct ast_frame null_frame = { AST_FRAME_NULL, }; switch (ast->fdno) { case 0: @@ -4279,25 +4452,61 @@ f = &null_frame; } - if (f && p->owner) { + if (f && p->owner && !p->faxmode && (f->frametype == AST_FRAME_VOICE)) { /* We already hold the channel lock */ - if (f->frametype == AST_FRAME_VOICE && !p->faxmode) { - if (f->subclass.codec != p->owner->nativeformats) { - ast_debug(1, "Oooh, voice format changed to %s\n", ast_getformatname(f->subclass.codec)); - p->owner->nativeformats = f->subclass.codec; - ast_set_read_format(p->owner, p->owner->readformat); - ast_set_write_format(p->owner, p->owner->writeformat); - } + if (f->subclass.codec != p->owner->nativeformats) { + ast_debug(1, "Oooh, voice format changed to %s\n", ast_getformatname(f->subclass.codec)); + p->owner->nativeformats = f->subclass.codec; + ast_set_read_format(p->owner, p->owner->readformat); + ast_set_write_format(p->owner, p->owner->writeformat); + } + if (((p->dtmfmode & H323_DTMF_INBAND) || p->faxdetect) && p->vad && + (f->subclass.codec == AST_FORMAT_SLINEAR || f->subclass.codec == AST_FORMAT_ALAW || + f->subclass.codec == AST_FORMAT_ULAW)) { + dfr = ast_frdup(f); + dfr = ast_dsp_process(p->owner, p->vad, dfr); + } + } else { + return f; + } - if ((p->dtmfmode & H323_DTMF_INBAND) && p->vad && - (f->subclass.codec == AST_FORMAT_SLINEAR || f->subclass.codec == AST_FORMAT_ALAW || - f->subclass.codec == AST_FORMAT_ULAW)) { - f = ast_dsp_process(p->owner, p->vad, f); - if (f && (f->frametype == AST_FRAME_DTMF)) - ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass.integer); + /* process INBAND DTMF*/ + if (dfr && (dfr->frametype == AST_FRAME_DTMF) && ((dfr->subclass.integer == 'f') || (dfr->subclass.integer == 'e'))) { + ast_debug(1, "* Detected FAX Tone %s\n", (dfr->subclass.integer == 'e') ? "CED" : "CNG"); + /* Switch to T.38 ON CED*/ + if (!p->faxmode && !p->chmodepend && (dfr->subclass.integer == 'e') && (p->t38support != T38_DISABLED)) { + if (gH323Debug) + ast_verbose("request to change %s to t.38 because fax ced\n", p->callToken); + p->chmodepend = 1; + ooRequestChangeMode(p->callToken, 1); + p->faxdetect = 0; + if (!(p->dtmfmode & H323_DTMF_INBAND)) { + ast_dsp_free(p->vad); + p->vad = NULL; } + } else if ((dfr->subclass.integer == 'f') && !p->faxdetected) { + const char *target_context = S_OR(p->owner->macrocontext, p->owner->context); + if ((strcmp(p->owner->exten, "fax")) && + (ast_exists_extension(p->owner, target_context, "fax", 1, + S_COR(p->owner->caller.id.number.valid, p->owner->caller.id.number.str, NULL)))) { + ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n", p->owner->name); + pbx_builtin_setvar_helper(p->owner, "FAXEXTEN", p->owner->exten); + if (ast_async_goto(p->owner, target_context, "fax", 1)) { + ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", p->owner->name,target_context); + } + p->faxdetected = 1; + return &ast_null_frame; + } } + } else if (dfr && dfr->frametype == AST_FRAME_DTMF) { + ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass.integer); + ast_frfree(f); + return dfr; } + + if (dfr) { + ast_frfree(dfr); + } return f; } @@ -4319,6 +4528,7 @@ if (gH323Debug) ast_debug(1, "mode for %s is already %d\n", call->callToken, t38mode); + p->chmodepend = 0; ast_mutex_unlock(&p->lock); return; } @@ -4329,11 +4539,13 @@ DEADLOCK_AVOIDANCE(&p->lock); } if (!p->owner) { + p->chmodepend = 0; ast_mutex_unlock(&p->lock); ast_log(LOG_ERROR, "Channel has no owner\n"); return; } } else { + p->chmodepend = 0; ast_mutex_unlock(&p->lock); ast_log(LOG_ERROR, "Channel has no owner\n"); return; Index: include/asterisk/res_fax.h =================================================================== --- a/include/asterisk/res_fax.h (revision 321680) +++ b/include/asterisk/res_fax.h (working copy) @@ -42,6 +42,8 @@ AST_FAX_TECH_T38 = (1 << 3), /*! sending mulitple documents supported */ AST_FAX_TECH_MULTI_DOC = (1 << 4), + /*! T.38 - T.30 Gateway */ + AST_FAX_TECH_GATEWAY = (1 << 5), }; /*! \brief fax modem capabilities */ @@ -158,6 +160,8 @@ uint32_t send_cng:1; /*! send a T.38 reinvite */ uint32_t request_t38:1; + /*! T.38 Gateway enable */ + uint32_t t38gateway:1; }; } option; /*! override the minimum transmission rate with a channel variable */ @@ -204,6 +208,27 @@ struct ast_smoother *smoother; }; +/* if this overlaps with any AST_FRFLAG_* values, problems will occur */ +#define AST_FAX_FRFLAG_GATEWAY (1 << 13) + +/*! \brief used for gateway framehook */ +struct ast_fax_session_gateway { + /*! FAX Session*/ + struct ast_fax_session *s; + /*! DSP Processor*/ + struct ast_dsp *chan_dsp; + struct ast_dsp *peer_dsp; + /*! framehook used in gateway mode */ + int framehook; + /*! bridged*/ + int bridged; + /*Original audio formats*/ + unsigned int chan_read_format; + unsigned int chan_write_format; + unsigned int peer_read_format; + unsigned int peer_write_format; +}; + /*! \brief used to register a FAX technology module with res_fax */ struct ast_fax_tech { /*! the type of fax session supported with this ast_fax_tech structure */ Index: main/frame.c =================================================================== --- a/main/frame.c (revision 321680) +++ b/main/frame.c (working copy) @@ -413,7 +413,7 @@ out->samples = fr->samples; out->offset = fr->offset; /* Copy the timing data */ - ast_copy_flags(out, fr, AST_FRFLAG_HAS_TIMING_INFO); + ast_copy_flags(out, fr, AST_FLAGS_ALL); if (ast_test_flag(fr, AST_FRFLAG_HAS_TIMING_INFO)) { out->ts = fr->ts; out->len = fr->len; @@ -537,7 +537,7 @@ /* Must have space since we allocated for it */ strcpy(src, f->src); } - ast_copy_flags(out, f, AST_FRFLAG_HAS_TIMING_INFO); + ast_copy_flags(out, f, AST_FLAGS_ALL); out->ts = f->ts; out->len = f->len; out->seqno = f->seqno; Index: res/res_fax.c =================================================================== --- a/res/res_fax.c (revision 321680) +++ b/res/res_fax.c (working copy) @@ -6,6 +6,22 @@ * Dwayne M. Hubbard * Kevin P. Fleming * + * 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 + * * 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; @@ -58,6 +74,7 @@ #include "asterisk/dsp.h" #include "asterisk/indications.h" #include "asterisk/ast_version.h" +#include "asterisk/translate.h" /*** DOCUMENTATION @@ -159,6 +176,9 @@ R/W Modem type (v17/v27/v29). + + R/W T38 Gateway Enabled (yes/no) + R/O Number of pages transferred. @@ -192,10 +212,25 @@ SendFax + + + Generic Fax Detect CNG/T.38 (Wait For Fax) + + + + Specifies the number of seconds we attempt to detect a fax tone on the channel + + + + This application sets the following channel variable upon completion + FAXDETECTED : 0 | 1 + + ***/ static const char app_receivefax[] = "ReceiveFAX"; static const char app_sendfax[] = "SendFAX"; +static const char app_waitfax[] = "WaitFAX"; struct debug_info_history { unsigned int consec_frames; @@ -389,10 +424,40 @@ d->modems = options.modems; d->minrate = options.minrate; d->maxrate = options.maxrate; + d->option.t38gateway = AST_FAX_OPTFLAG_FALSE; 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) @@ -415,6 +480,11 @@ } /* add the datastore to the channel and increment the refcount */ datastore->data = details; + + /*init default T38 paramaters*/ + 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); @@ -507,6 +577,13 @@ ast_build_string(&buf, &size, "MULTI_DOC"); first = 0; } + if (caps & AST_FAX_TECH_GATEWAY) { + if (!first) { + ast_build_string(&buf, &size, ","); + } + ast_build_string(&buf, &size, "T38_GATEWAY"); + first = 0; + } return out; } @@ -1002,37 +1079,22 @@ 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)) { case T38_STATE_UNKNOWN: details->caps |= AST_FAX_TECH_T38; break; + case T38_STATE_REJECTED: case T38_STATE_UNAVAILABLE: details->caps |= AST_FAX_TECH_AUDIO; + /* The T.38 Gateway provides T.38 so if its active T.38 is a valid option*/ + if (details->option.t38gateway) { + details->caps |= AST_FAX_TECH_T38; + } break; + case T38_STATE_NEGOTIATED: + /* i am already T.38 have i been answered already and switched to T.38 ?? */ 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 @@ -1114,13 +1176,6 @@ 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) { @@ -1386,8 +1441,6 @@ 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 */ @@ -1547,6 +1600,7 @@ ); struct ast_flags opts = { 0, }; struct manager_event_info info; + enum ast_t38_state t38state; /* initialize output channel variables */ pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); @@ -1679,7 +1733,8 @@ details->option.statusevents = AST_FAX_OPTFLAG_TRUE; } - if ((ast_channel_get_t38_state(chan) == T38_STATE_UNAVAILABLE) || + t38state = ast_channel_get_t38_state(chan); + if ((t38state == T38_STATE_UNAVAILABLE) || (t38state == T38_STATE_REJECTED) || ast_test_flag(&opts, OPT_ALLOWAUDIO)) { details->option.allow_audio = AST_FAX_OPTFLAG_TRUE; } @@ -1785,8 +1840,6 @@ 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. @@ -2019,6 +2072,7 @@ ); struct ast_flags opts = { 0, }; struct manager_event_info info; + enum ast_t38_state t38state; /* initialize output channel variables */ pbx_builtin_setvar_helper(chan, "FAXSTATUS", "FAILED"); @@ -2171,7 +2225,8 @@ details->option.statusevents = AST_FAX_OPTFLAG_TRUE; } - if ((ast_channel_get_t38_state(chan) == T38_STATE_UNAVAILABLE) || + t38state = ast_channel_get_t38_state(chan); + if ((t38state == T38_STATE_UNAVAILABLE) || (t38state == T38_STATE_REJECTED) || ast_test_flag(&opts, OPT_ALLOWAUDIO)) { details->option.allow_audio = AST_FAX_OPTFLAG_TRUE; } @@ -2283,6 +2338,571 @@ return (!channel_alive) ? -1 : 0; } +/*! \brief channel datastore holding a T38 fax gateway object*/ +static const struct ast_datastore_info fax_datastore_gateway = { + .type = "res_fax_gateway", + .destroy = destroy_callback, +}; + +/*! \brief returns a reference counted gateway structure from the channel's fax gateway datastore. + * \param chan channel to look for the datastore + * \param create flag if set the datastore will be created if not found + * \return fax gateway object or null if it does not exist or cant be created depending on create.*/ +static struct ast_fax_session_gateway *find_session_gateway(struct ast_channel *chan, int create) +{ + struct ast_fax_session_gateway *gateway; + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &fax_datastore_gateway, NULL); + ast_channel_unlock(chan); + if (create && ((!datastore || !datastore->data))) { + gateway = ao2_alloc(sizeof(*gateway), NULL); + if (!gateway) { + return NULL; + } + + if (!(datastore = ast_datastore_alloc(&fax_datastore_gateway, NULL))) { + ast_log(LOG_WARNING, "channel '%s' can't get a datastore!\n", chan->name); + ao2_ref(gateway, -1); + return NULL; + } + + /* add the datastore to the channel*/ + datastore->data = gateway; + + gateway->framehook = -1; + gateway->chan_dsp = NULL; + gateway->peer_dsp = NULL; + gateway->s = NULL; + gateway->bridged = 0; + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + } else if (datastore && datastore->data) { + gateway = datastore->data; + } else { + return NULL; + } + + /* i need a ref here for create and find as the datastore must hold one */ + ao2_ref(gateway, 1); + return gateway; +} + +/*! \brief create a fax session and start T.30<->T.38 gateway mode + * \param details fax session details + * \param chan active channel + * \return 0 on error 1 on success*/ +static int ast_t38_gateway(struct ast_fax_session_details *details, struct ast_channel *chan) +{ + struct ast_fax_session_gateway *gateway; + + gateway = find_session_gateway(chan, 0); + if (!gateway) { + return 0; + } + + if (gateway->s) { + ao2_ref(gateway, -1); + return 0; + } + + /* create the FAX session */ + details->caps = AST_FAX_TECH_GATEWAY; + if (!(gateway->s = fax_session_new(details, chan, NULL, NULL))) { + ast_log(LOG_ERROR, "Can't create a FAX session, FAX attempt failed.\n"); + report_fax_status(chan, details, "No Available Resource"); + ao2_ref(gateway, -1); + return 0; + } + + if (gateway->s->tech->start_session(gateway->s) < 0) { + ao2_ref(gateway->s, -1); + gateway->s = NULL; + ao2_ref(gateway, -1); + return 0; + } + + /* looks like we good to go */ + gateway->s->state = AST_FAX_STATE_ACTIVE; + ao2_ref(gateway, -1); + return 1; +} + +/*! \brief T38 Gateway Negotiate t38 parameters + * \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 *ast_t38_gateway_parameters(struct ast_channel *chan,struct ast_channel *peer, struct ast_channel *active, struct ast_frame *f) +{ + struct ast_control_t38_parameters *active_param; + struct ast_fax_t38_parameters *t38_param; + struct ast_channel *inactive; + enum ast_t38_state activestate, inactivestate; + struct ast_fax_session_details *details; + + /* Get a FAX session details structure from the channel's FAX datastore*/ + if (!(details = find_details(chan))) { + return f; + } + + /* i am not a faxgateway */ + if (!details->option.t38gateway) { + ast_debug(1, "T.38 Parameter without gateway enabled\n"); + ao2_ref(details, -1); + return f; + } + + /* T.38 States*/ + inactive = (active == chan) ? peer : chan; + activestate = ast_channel_get_t38_state(active); + inactivestate = ast_channel_get_t38_state(inactive); + t38_param = (active == chan) ? &details->our_t38_parameters : &details->their_t38_parameters; + + active_param=f->data.ptr; + switch (active_param->request_response) { + /* ill need to do something inteligent with AST_T38_REQUEST_TERMINATE/AST_T38_TERMINATED like turn off frame hooks and reply */ + case AST_T38_REFUSED: + /*this is not good i should never get a refused connection ill fake negotiated other side need not know */ + ast_verb(3, "Refused on %s [%i] Sending Negotiated I: %s [%i]\n", active->name, activestate, inactive->name, inactivestate); + t38_parameters_fax_to_ast(active_param, t38_param); + active_param->request_response = AST_T38_NEGOTIATED; + break; + case AST_T38_REQUEST_NEGOTIATE: + case AST_T38_NEGOTIATED: + if ((inactivestate == T38_STATE_UNAVAILABLE) || (inactivestate == T38_STATE_REJECTED)) { + /* im not capable of T.38 so use the options from FAXOPT and queue on other side for me (faking it)*/ + ast_verb(3, "%s on %s [%i] %s I: %s [%i]\n", + (active_param->request_response == AST_T38_NEGOTIATED) ? "Negotiated" : "Request", + active->name, activestate, (activestate == T38_STATE_NEGOTIATED) ? "Ignoring" : "Queueing", + inactive->name, inactivestate); + t38_parameters_fax_to_ast(active_param, t38_param); + /*prevent a negotiation storm only talk to unstable ends*/ + if ((activestate == T38_STATE_NEGOTIATING) || (activestate == T38_STATE_UNKNOWN)) { + ast_queue_control_data(inactive, AST_CONTROL_T38_PARAMETERS, active_param, sizeof(struct ast_control_t38_parameters)); + } + ast_frfree(f); + f = &ast_null_frame; + } else { + /*im capable of T.38 make the paramaters mine*/ + ast_verb(3, "%s on %s [%i] Storing I: %s [%i]\n", + (active_param->request_response == AST_T38_NEGOTIATED) ? "Negotiated" : "Request", + active->name, activestate, inactive->name, inactivestate); + t38_parameters_ast_to_fax(t38_param, active_param); + } + break; + default: + ast_verb(3, "Unhandled Request Response [%i] on %s [%i] I: %s [%i]\n", active_param->request_response, + active->name, activestate, inactive->name, inactivestate); + break; + } + + /* Start gateway if both channels are in a stable T.38 (not negotiating) state and only one of them is not negotiated! */ + if (((activestate == T38_STATE_NEGOTIATED) && ((inactivestate == T38_STATE_UNAVAILABLE) || (inactivestate == T38_STATE_REJECTED))) || + ((inactivestate == T38_STATE_NEGOTIATED) && ((activestate == T38_STATE_UNAVAILABLE) || (activestate == T38_STATE_REJECTED)))) { + if (ast_t38_gateway(details, chan)) { + ast_verb(3, "T.38 Gateway starting for chan %s and peer %s\n", chan->name, peer->name); + } + } + + 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 t38_gw_fh_destroy(void *data) { + struct ast_fax_session_gateway *gateway = data; + struct ast_fax_session *s; + + if (!gateway) { + return; + } + + 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) { + s = gateway->s; + s->tech->destroy_session(s); + ao2_lock(faxregistry.container); + ao2_unlink(faxregistry.container, s); + ao2_unlock(faxregistry.container); + ao2_ref(s, -1); + } + ao2_ref(gateway, -1); +} + +/*! \brief T.30<->T.38 gateway frame hook + * \details intercept packets on bridged channels and determine if a T.38 gateway is required. + * when a CED faxtone is detected send a T.38 negotiate from the non T.38 channel. + * all T.38 negotiation needs to be handled where one channel is not T.38 capable. + * the gateway object will transmit frames i need to read native frames from both channels. + * \param chan channel im attached too + * \param f frame to handle may be NULL + * \param event framehook event + * \param data framehook data (gateway) + * \return processed frame or NULL when f is NULL or a null frame*/ +static struct ast_frame *t38_gw_framehook(struct ast_channel *chan,struct ast_frame *f, + enum ast_framehook_event event,void *data) { + struct ast_control_t38_parameters *t38_parameters; + enum ast_t38_state t38state; + enum ast_t38_state t38pstate; + struct ast_fax_session_gateway *gateway = data; + struct ast_fax_session *s; + struct ast_frame *dfr = NULL; + struct ast_channel *peer, *active, *inactive; + struct ast_dsp *dsp; + + if (!f || (event == AST_FRAMEHOOK_EVENT_ATTACHED) || (event == AST_FRAMEHOOK_EVENT_DETACHED)) { + return NULL; + }; + + if (!gateway) { + return f; + } + + /* this frame was generated by the fax gateway, pass it on */ + if (ast_test_flag(f, AST_FAX_FRFLAG_GATEWAY)) { + return f; + } + + peer = ast_bridged_channel(chan); + s = gateway->s; + + switch (event) { + case AST_FRAMEHOOK_EVENT_WRITE: + /* when we become active change the formats to SLIN for CED detect and T.30*/ + if (peer && !gateway->bridged) { + gateway->chan_read_format = chan->readformat; + gateway->chan_write_format = chan->writeformat; + gateway->peer_read_format = peer->readformat; + gateway->peer_write_format = peer->writeformat; + ast_set_read_format(chan, AST_FORMAT_SLINEAR); + ast_set_write_format(chan, AST_FORMAT_SLINEAR); + ast_set_read_format(peer, AST_FORMAT_SLINEAR); + ast_set_write_format(peer, AST_FORMAT_SLINEAR); + ast_channel_make_compatible(chan, peer); + gateway->bridged = 1; + } + active = peer; + break; + case AST_FRAMEHOOK_EVENT_READ: + active = chan; + break; + default: + ast_log(LOG_WARNING, "Unhandled Framehook Event %i\n",event); + return f; + } + + /* filter frames*/ + switch (f->frametype) { + case AST_FRAME_VOICE: + switch (f->subclass.codec) { + case AST_FORMAT_SLINEAR: + case AST_FORMAT_ALAW: + case AST_FORMAT_ULAW: + break; + default: + return f; + } + break; + case AST_FRAME_MODEM: + if (s && (f->subclass.codec == AST_MODEM_T38)) { + break; + } + return f; + case AST_FRAME_CONTROL: + break; + default: + return f; + } + + /*handle control frames*/ + if (f->frametype == AST_FRAME_CONTROL) { + switch (f->subclass.integer) { + case AST_CONTROL_T38_PARAMETERS: + /* invalaid frame im either commited to gateway or incorect size*/ + if (s || (f->datalen != sizeof(struct ast_control_t38_parameters))) { + ast_frfree(f); + return &ast_null_frame; + } + /* i have got a T.38 request before im bridged ?? perhaps im in a app or func*/ + if (!peer) { + return f; + } + f = ast_t38_gateway_parameters(chan,peer,active,f); + default: + return f; + } + } + + /* im not a gateway let me listen for CED*/ + dsp = (active == chan) ? gateway->chan_dsp : gateway->peer_dsp; + if (!s && chan && peer && dsp && (f->frametype == AST_FRAME_VOICE)) { + dfr = ast_frdup(f); + dfr = ast_dsp_process(active, dsp, dfr); + if (dfr && (dfr->frametype == AST_FRAME_DTMF) && (dfr->subclass.integer == 'e')) { + inactive = (chan == active) ? peer : chan; + t38state = ast_channel_get_t38_state(active); + t38pstate = ast_channel_get_t38_state(inactive); + t38_parameters = &our_t38_parameters; + t38_parameters->request_response = AST_T38_REQUEST_NEGOTIATE; + /* queue the negotiation on a channel with no T.38 to get the other side up and going*/ + if ((t38state == T38_STATE_UNAVAILABLE) || (t38state == T38_STATE_REJECTED)) { + ast_queue_control_data(active, AST_CONTROL_T38_PARAMETERS, t38_parameters, sizeof(our_t38_parameters)); + } else if ((t38pstate == T38_STATE_UNAVAILABLE) || (t38pstate == T38_STATE_REJECTED)) { + ast_queue_control_data(inactive, AST_CONTROL_T38_PARAMETERS, t38_parameters, sizeof(our_t38_parameters)); + } + ast_verb(3 ,"Got Fax Tone CED Chan %s [%i] Sending T.38 Params Peer Is %s [%i]\n", + active->name, t38state, (inactive) ? inactive->name : "NONE", t38pstate); + 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 (dfr) { + ast_frfree(dfr); + } + return f; + } else if (!s || !peer) { + return f; + } + + /*get t38states i may not be bridged yet [these should be stored on gw start to minimise this hook]*/ + t38state = ast_channel_get_t38_state(active); + t38pstate = ast_channel_get_t38_state((active == chan) ? peer : chan); + + /*Im not a gateway and this is not audio channel*/ + if ((t38state == T38_STATE_NEGOTIATED) && (t38pstate != T38_STATE_UNAVAILABLE) && + (t38pstate != T38_STATE_REJECTED)) { + return f; + } + + switch(t38state) { + case T38_STATE_UNAVAILABLE: + case T38_STATE_REJECTED: + /*Im not a gateway and this is a audio channel*/ + if (t38pstate != T38_STATE_NEGOTIATED) { + break; + } + /* i need to translate here as this is done latter in __ast_read*/ + if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.codec != AST_FORMAT_SLINEAR)) { + if (active->readtrans && (f = ast_translate(active->readtrans, f, 1)) == NULL) { + if (!f) { + f = &ast_null_frame; + } + break; + } + } + case T38_STATE_NEGOTIATED: + /*process the gateway packet*/ + if (s->tech->write(s, f)) { + if (event == AST_FRAMEHOOK_EVENT_READ) { + ast_frfree(f); + } + f = &ast_null_frame; + } + break; + default: + ast_debug(1, "Incompatible T.38 State for gateway mode\n"); + } + + return f; +} + +/*! \brief attach a gateway framehook object to the channel + * \details create and setup the gateway framehook and datastore + * \param chan channel im attached too*/ +static void gateway_attach_hook(struct ast_channel *chan) +{ + struct ast_fax_session_gateway *gateway; + struct ast_framehook_interface fr_hook = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = t38_gw_framehook, + .destroy_cb = t38_gw_fh_destroy, + }; + + /* set up the frame hook*/ + gateway = find_session_gateway(chan, 1); + if (gateway && (gateway->framehook < 0)) { + fr_hook.data = gateway; + ast_channel_lock(chan); + gateway->framehook = ast_framehook_attach(chan, &fr_hook); + ast_channel_unlock(chan); + if (gateway->framehook >= 0) { + gateway->chan_dsp = ast_dsp_new(); + if (gateway->chan_dsp) { + ast_dsp_set_features(gateway->chan_dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(gateway->chan_dsp, DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED); + } + gateway->peer_dsp = ast_dsp_new(); + if (gateway->peer_dsp) { + ast_dsp_set_features(gateway->peer_dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(gateway->peer_dsp, DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED); + } + } else { + /* im not in a frame hook*/ + ao2_ref(gateway, -1); + } + } else if (gateway) { + ao2_ref(gateway, -1); + } +} + +/*! \brief detach a gateway framehook object to the channel + * \details remove and destroy the gateway framehook and datastore + * \param chan channel im attached too*/ +static void gateway_remove_hook(struct ast_channel *chan) +{ + struct ast_fax_session_gateway *gateway; + struct ast_datastore *datastore; + struct ast_channel *peer; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &fax_datastore_gateway, NULL); + if (datastore) { + ast_channel_datastore_remove(chan, datastore); + if (datastore->data) { + gateway = datastore->data; + ast_datastore_free(datastore); + /* restore audio formats*/ + if (gateway->bridged) { + ast_set_read_format(chan, gateway->chan_read_format); + ast_set_read_format(chan, gateway->chan_write_format); + peer = ast_bridged_channel(chan); + if (peer) { + ast_set_read_format(peer, gateway->peer_read_format); + ast_set_read_format(peer, gateway->peer_write_format); + ast_channel_make_compatible(chan, peer); + } + } + ast_framehook_detach(chan, gateway->framehook); + } + } + ast_channel_unlock(chan); +} + +/*! \brief Alternate wait app that listens for CNG + * \details This app answers the channel and waits for CNG tone + * if the channel driver supports faxdetect it will proceed to the fax + * extension. + * it will return FAXDETECTED = 0 | 1 to allow dialplan processing where + * the channel does not have fax detect or the Dialplan needs to handle faxdetection*/ +static int waitfax_exec(struct ast_channel *chan, const char *data) +{ + char *parse; + int timeout, tone = 0; + struct ast_dsp *dsp = NULL; + struct ast_frame *f; + enum ast_t38_state t38state = T38_STATE_UNKNOWN; + struct ast_silence_generator *silgen = NULL; + unsigned int orig_read_format = 0; + AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(timeout); + ); + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (args.timeout) { + timeout = atoi(args.timeout) * 1000; + } else { + pbx_builtin_setvar_helper(chan, "FAXDETECTED", "0"); + return 0; + } + + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + /* Setup DSP CNG processing */ + orig_read_format = chan->readformat; + if (!ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + if ((dsp=ast_dsp_new())) { + ast_dsp_set_features(dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(dsp, DSP_FAXMODE_DETECT_CNG); + } + } + + AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames); + + /* If no other generator is present, start silencegen while waiting */ + if (ast_opt_transmit_silence && !chan->generatordata) { + silgen = ast_channel_start_silence_generator(chan); + } + + while ((timeout = ast_waitfor(chan, timeout))) { + if (!(f=ast_read(chan))) { + pbx_builtin_setvar_helper(chan, "FAXDETECTED", "0"); + break; + } + + if (dsp && (f->frametype == AST_FRAME_VOICE) && ((f->subclass.codec == AST_FORMAT_SLINEAR) || + (f->subclass.codec == AST_FORMAT_ULAW) || (f->subclass.codec == AST_FORMAT_ALAW))) { + f = ast_dsp_process(chan, dsp, f); + if ((f->frametype == AST_FRAME_DTMF) && (f->subclass.integer == 'f')) { + tone=1; + ast_dsp_free(dsp); + dsp = NULL; + } + } + + if (ast_is_deferrable_frame(f)) { + AST_LIST_INSERT_HEAD(&deferred_frames, f, frame_list); + } else { + ast_frfree(f); + } + + t38state = ast_channel_get_t38_state(chan); + if ((t38state == T38_STATE_NEGOTIATING) || (t38state == T38_STATE_NEGOTIATED) || tone) { + pbx_builtin_setvar_helper(chan, "FAXDETECTED", "1"); + break; + } + } + + if (timeout <= 0) { + pbx_builtin_setvar_helper(chan, "FAXDETECTED", "0"); + } + + /* stop silgen if present */ + if (silgen) { + ast_channel_stop_silence_generator(chan, silgen); + } + + if (orig_read_format) { + ast_set_read_format(chan, orig_read_format); + } + + ast_channel_lock(chan); + while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) { + ast_queue_frame_head(chan, ast_frisolate(f)); + } + ast_channel_unlock(chan); + + if (dsp) { + ast_dsp_free(dsp); + } + + return 0; +} + /*! \brief hash callback for ao2 */ static int session_hash_cb(const void *obj, const int flags) { @@ -2679,6 +3299,9 @@ } 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->option.t38gateway ? "yes" : "no", len); } else if (!strcasecmp(data, "error")) { ast_copy_string(buf, details->error, len); } else if (!strcasecmp(data, "filename")) { @@ -2753,6 +3376,18 @@ } 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)) { + gateway_attach_hook(chan); + details->option.t38gateway = AST_FAX_OPTFLAG_TRUE; + } else if (ast_false(val)) { + gateway_remove_hook(chan); + details->option.t38gateway = AST_FAX_OPTFLAG_FALSE; + } else { + ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(t38gateway).\n", value); + } } else if (!strcasecmp(data, "headerinfo")) { ast_string_field_set(details, headerinfo, value); } else if (!strcasecmp(data, "localstationid")) { @@ -2803,6 +3438,10 @@ ast_log(LOG_WARNING, "failed to unregister '%s'\n", app_receivefax); } + if (ast_unregister_application(app_waitfax) < 0) { + ast_log(LOG_WARNING, "failed to unregister '%s'\n", app_waitfax); + } + if (fax_logger_level != -1) { ast_logger_unregister_level("FAX"); } @@ -2842,6 +3481,11 @@ ao2_ref(faxregistry.container, -1); return AST_MODULE_LOAD_DECLINE; } + + if (ast_register_application_xml(app_waitfax, waitfax_exec) < 0) { + ast_log(LOG_WARNING, "failed to register '%s'.\n", app_waitfax); + } + 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"); Index: res/res_fax_spandsp.c =================================================================== --- a/res/res_fax_spandsp.c (revision 321680) +++ b/res/res_fax_spandsp.c (working copy) @@ -5,6 +5,22 @@ * * Matthew Nicholson * + * 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 + * * 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 @@ #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_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_clean(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 @@ */ .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, @@ -121,6 +142,9 @@ 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 +182,8 @@ */ 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; + 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, @@ -177,6 +202,17 @@ return -1; } + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + ast_set_flag(f, AST_FAX_FRFLAG_GATEWAY); + if (ast_channel_get_t38_state(s->chan) == T38_STATE_NEGOTIATED) { + return ast_write(s->chan, f); + } else { + int res = ast_queue_frame(s->chan, f); + ast_frfree(f); + return res; + } + } + /* no need to lock, this all runs in the same thread */ AST_LIST_INSERT_TAIL(&p->read_frames, f, frame_list); @@ -428,6 +464,8 @@ caller_mode = 0; } else if (s->details->caps & AST_FAX_TECH_SEND) { caller_mode = 1; + } else if (s->details->caps & AST_FAX_TECH_GATEWAY) { + caller_mode = 2; } else { ast_log(LOG_ERROR, "Are we sending or receiving? The FAX requirements (capabilities: 0x%X) were not properly set.\n", s->details->caps); goto e_free; @@ -450,7 +488,7 @@ } /* 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,6 +513,11 @@ { struct spandsp_pvt *p = s->tech_pvt; + if (s->details->caps & AST_FAX_TECH_GATEWAY) { + spandsp_fax_gateway_clean(s); + return; + } + session_destroy(p); ast_free(p); s->tech_pvt = NULL; @@ -537,6 +580,10 @@ { 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)); @@ -550,6 +597,176 @@ } } +/*! \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) +{ + 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, + .subclass.codec = AST_FORMAT_SLINEAR, + .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)); + + /* generate a T.30 packet */ + f = ast_frisolate(&t30_frame); + if ((f->samples = t38_gateway_tx(&p->t38_gw_state, f->data.ptr, f->samples))) { + f->datalen = f->samples * sizeof(int16_t); + if (ast_write(chan, f) < 0) { + ast_frfree(f); + return -1; + } + ast_frfree(f); + return 0; + } + return -1; +} + +/*! \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) { + return params; +} + +/*! \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; + enum ast_t38_state t38state; + struct ast_channel *peer; + static struct ast_generator t30_gen = { + alloc: spandsp_fax_gw_gen_alloc, + 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; + ast_channel_lock(s->chan); + t38state = ast_channel_get_t38_state(s->chan); + if (!(peer = ast_bridged_channel(s->chan))) { + ast_channel_unlock(s->chan); + return -1; + } + ast_activate_generator((t38state == T38_STATE_NEGOTIATED) ? peer : s->chan, &t30_gen , s); + ast_channel_unlock(s->chan); + + span_log_set_message_handler(&p->t38_gw_state.logging, spandsp_log); + span_log_set_message_handler(&p->t38_core_state->logging, spandsp_log); + span_log_set_level(&p->t38_gw_state.logging, SPAN_LOG_WARNING + option_debug); + span_log_set_level(&p->t38_core_state->logging, SPAN_LOG_WARNING + option_debug); + + t38_param = (ast_channel_get_t38_state(s->chan) == 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); + + /* need to be configrable FAXOPT ??*/ + t38_gateway_set_transmit_on_idle(&p->t38_gw_state, TRUE); + t38_set_sequence_number_handling(p->t38_core_state, TRUE); + t38_gateway_set_supported_modems(&p->t38_gw_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 < 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 + } + + 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 0; + } + + /* Process a IFP packet */ + if ((f->frametype == AST_FRAME_MODEM) && (f->subclass.codec == AST_MODEM_T38)) { + t38_core_rx_ifp_packet(p->t38_core_state, f->data.ptr, f->datalen, f->seqno); + return 1; + } else if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.codec == AST_FORMAT_SLINEAR)) { + t38_gateway_rx(&p->t38_gw_state, f->data.ptr, f->samples); + return 1; + } + return 0; +} + +/*! \brief gather data and clean up after gateway ends + * \param s fax session*/ +static void spandsp_fax_gateway_clean(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + t38_stats_t t38_stats; + struct ast_frame *f; + + if (!p) { + return; + } + + t38_gateway_get_transfer_statistics(&p->t38_gw_state, &t38_stats); + ast_verb(3, "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); + + p->isdone = 1; + ast_timer_close(p->timer); + + while ((f = AST_LIST_REMOVE_HEAD(&p->read_frames, frame_list))) { + ast_frfree(f); + } + + free(p); + s->tech_pvt = NULL; +} + /*! \brief */ static int spandsp_fax_start(struct ast_fax_session *s) { @@ -557,6 +774,10 @@ 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 */