Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (.../branches/1.8) (revision 326143) +++ channels/chan_sip.c (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -4307,6 +4307,9 @@ case T38_ENABLED: state = T38_STATE_NEGOTIATED; break; + case T38_REJECTED: + state = T38_STATE_REJECTED; + break; default: state = T38_STATE_UNKNOWN; } @@ -4969,6 +4972,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; @@ -6534,11 +6538,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")); @@ -6576,7 +6580,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); @@ -9012,7 +9016,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; } @@ -18856,7 +18860,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); @@ -19622,7 +19626,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); @@ -21421,7 +21425,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"); @@ -22297,7 +22301,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 =================================================================== --- channels/sip/include/sip.h (.../branches/1.8) (revision 326143) +++ channels/sip/include/sip.h (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -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: addons/chan_ooh323.c =================================================================== --- addons/chan_ooh323.c (.../branches/1.8) (revision 326143) +++ addons/chan_ooh323.c (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -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 (p->owner) { + if (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 =================================================================== --- include/asterisk/res_fax.h (.../branches/1.8) (revision 326143) +++ include/asterisk/res_fax.h (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -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 */ @@ -168,6 +170,8 @@ struct ast_fax_t38_parameters our_t38_parameters; /*! the other endpoint's T.38 session parameters, if any */ struct ast_fax_t38_parameters their_t38_parameters; + /*! the id of the t.38 gateway framehook for this channel */ + int gateway_id; }; struct ast_fax_tech; @@ -204,6 +208,9 @@ struct ast_smoother *smoother; }; +/* if this overlaps with any AST_FRFLAG_* values, problems will occur */ +#define AST_FAX_FRFLAG_GATEWAY (1 << 13) + /*! \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 =================================================================== --- main/frame.c (.../branches/1.8) (revision 326143) +++ main/frame.c (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -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 =================================================================== --- res/res_fax.c (.../branches/1.8) (revision 326143) +++ res/res_fax.c (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -5,7 +5,24 @@ * * Dwayne M. Hubbard * Kevin P. Fleming + * 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-2011, 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; @@ -27,6 +44,8 @@ * * \author Dwayne M. Hubbard * \author Kevin P. Fleming + * \author Matthew Nicholson + * \author Gregory H. Nietsky * * 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 +77,7 @@ #include "asterisk/dsp.h" #include "asterisk/indications.h" #include "asterisk/ast_version.h" +#include "asterisk/translate.h" /*** DOCUMENTATION @@ -159,6 +179,9 @@ R/W Modem type (v17/v27/v29). + + R/W T38 Gateway Enabled (yes/no) + R/O Number of pages transferred. @@ -192,10 +215,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; @@ -209,12 +247,37 @@ 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 */ + unsigned int chan_read_format; + unsigned int chan_write_format; + unsigned int peer_read_format; + unsigned int 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 { @@ -389,10 +452,40 @@ 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) @@ -415,6 +508,11 @@ } /* 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); @@ -507,6 +605,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; } @@ -682,6 +787,19 @@ } } +/*! \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) { @@ -728,6 +846,19 @@ 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; @@ -739,6 +870,8 @@ } 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 @@ -777,7 +910,22 @@ 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; @@ -788,6 +936,12 @@ 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; @@ -830,8 +984,10 @@ } 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); @@ -931,9 +1087,6 @@ 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) { @@ -941,25 +1094,31 @@ 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; } @@ -1002,37 +1161,18 @@ 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; 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 @@ -1114,13 +1254,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 +1519,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 +1678,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"); @@ -1575,6 +1707,14 @@ 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_atomic_fetchadd_int(&faxregistry.fax_failures, 1); ast_string_field_set(details, error, "INVALID_ARGUMENTS"); @@ -1679,7 +1819,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 +1926,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 +2158,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"); @@ -2047,6 +2187,14 @@ 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_atomic_fetchadd_int(&faxregistry.fax_failures, 1); ast_string_field_set(details, error, "INVALID_ARGUMENTS"); @@ -2171,7 +2319,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 +2432,752 @@ 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 */ + gateway->chan_read_format = chan->readformat; + gateway->chan_write_format = chan->readformat; + + gateway->peer_read_format = peer->readformat; + gateway->peer_write_format = peer->readformat; + + 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; + } + + 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.codec) { + 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.codec != 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 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) { @@ -2554,19 +3449,15 @@ 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); @@ -2679,6 +3570,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->gateway_id != -1 ? "yes" : "no", len); } else if (!strcasecmp(data, "error")) { ast_copy_string(buf, details->error, len); } else if (!strcasecmp(data, "filename")) { @@ -2753,6 +3647,27 @@ } 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")) { @@ -2803,6 +3718,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,8 +3761,13 @@ 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); + res = ast_custom_function_register(&acf_faxopt); fax_logger_level = ast_logger_register_level("FAX"); return res; Index: res/res_fax_spandsp.c =================================================================== --- res/res_fax_spandsp.c (.../branches/1.8) (revision 326143) +++ res/res_fax_spandsp.c (.../team/irroot/t38gateway-1.8) (revision 326143) @@ -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; @@ -21,6 +37,7 @@ * \brief Spandsp T.38 and G.711 FAX Resource * * \author Matthew Nicholson + * \author Gregory H. Nietsky * * This module registers the Spandsp FAX technology with the res_fax module. */ @@ -46,9 +63,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 +76,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_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 +97,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, @@ -114,6 +136,7 @@ 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 +144,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 +184,9 @@ */ 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 +202,23 @@ 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 +460,11 @@ 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 +493,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,7 +518,12 @@ { 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; @@ -537,6 +585,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 +602,182 @@ } } +/*! \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, + .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)); + + 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.codec == 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) { @@ -557,6 +785,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 */ @@ -626,6 +858,12 @@ 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; @@ -654,7 +892,7 @@ /*! \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; } @@ -662,35 +900,48 @@ 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");