Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 100974) +++ channels/chan_sip.c (working copy) @@ -1688,6 +1688,7 @@ static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int sip_senddigit_begin(struct ast_channel *ast, char digit); static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); +static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen); static char *sip_get_callid(struct ast_channel *chan); static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin); @@ -2008,6 +2009,7 @@ static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans); static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan); static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl); +static void change_t38_state(struct sip_pvt *p, int state); /*------ Session-Timers functions --------- */ static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp); @@ -2049,6 +2051,7 @@ .early_bridge = ast_rtp_early_bridge, .send_text = sip_sendtext, /* called with chan locked */ .func_channel_read = acf_channel_read, + .queryoption = sip_queryoption, .get_pvt_uniqueid = sip_get_callid, }; @@ -3082,6 +3085,54 @@ return res; } +static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen) +{ + int res = -1; + enum ast_t38state state; + + struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt; + + + switch (option) { + case AST_OPTION_T38_STATE: + if (*datalen != sizeof(enum ast_t38state)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", sizeof(enum ast_t38state), *datalen); + return -1; + } + + sip_pvt_lock(p); + + if (!ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT)) { + state = T38_UNAVAILABLE; + } else { + switch (p->t38.state) { + + case T38_LOCAL_DIRECT: + case T38_LOCAL_REINVITE: + case T38_PEER_DIRECT: + case T38_PEER_REINVITE: + state = T38_NEGOTIATING; + break; + + case T38_ENABLED: + state = T38_NEGOTIATED; + break; + + default: + state = T38_UNKNOWN; + } + } + + sip_pvt_unlock(p); + + *((enum ast_t38state *) data) = state; + + break; + } + + return res; +} + /*! \brief Locate closing quote in a string, skipping escaped quotes. * optionally with a limit on the search. * start must be past the first quote. @@ -3774,6 +3825,35 @@ } } +static void change_t38_state(struct sip_pvt *p, int state) +{ + int old = p->t38.state; + struct ast_channel *chan = p->owner; + int message; + + if (old == state) + return; + + p->t38.state = state; + ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : ""); + + if (chan) { + message = 0; + + if (state == T38_ENABLED) + message = AST_CONTROL_T38_NEGOTIATED; + else if (state == T38_DISABLED && old == T38_ENABLED) + message = AST_CONTROL_T38_TERMINATED; + else if (state == T38_DISABLED && old == T38_LOCAL_REINVITE) + message = AST_CONTROL_T38_REFUSED; + + if (message) { + ast_queue_control(chan, AST_CONTROL_T38_NEGOTIATED); + } + } + +} + /*! \brief Set the global T38 capabilities on a SIP dialog structure */ static void set_t38_capabilities(struct sip_pvt *p) { @@ -4722,8 +4802,7 @@ ast_setstate(ast, AST_STATE_UP); ast_debug(1, "SIP answering channel: %s\n", ast->name); if (p->t38.state == T38_PEER_DIRECT) { - p->t38.state = T38_ENABLED; - ast_debug(2,"T38State change to %d on channel %s\n", p->t38.state, ast->name); + change_t38_state(p, T38_ENABLED); res = transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL); } else res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE); @@ -5008,6 +5087,17 @@ } else res = -1; break; + case AST_CONTROL_T38_REQUEST_NEGOTIATE: /* Request T38 */ + if (p->t38.state != T38_ENABLED) { + change_t38_state(p, T38_LOCAL_REINVITE); + transmit_reinvite_with_sdp(p, TRUE, FALSE); + } + break; + case AST_CONTROL_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (p->t38.state == T38_ENABLED) { + transmit_reinvite_with_sdp(p, FALSE, FALSE); + } + break; case -1: res = -1; break; @@ -5440,9 +5530,8 @@ if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) { if (!p->pendinginvite) { ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name); - p->t38.state = T38_LOCAL_REINVITE; + change_t38_state(p, T38_LOCAL_REINVITE); transmit_reinvite_with_sdp(p, TRUE, FALSE); - ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name); } } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { ast_debug(3, "Deferring reinvite on SIP (%s) - it will be re-negotiated for T.38\n", ast->name); @@ -6608,24 +6697,21 @@ p->t38.peercapability, p->t38.jointcapability); - /* Remote party offers T38, we need to update state */ if (t38action == SDP_T38_ACCEPT) { if (p->t38.state == T38_LOCAL_DIRECT || p->t38.state == T38_LOCAL_REINVITE) - p->t38.state = T38_ENABLED; + change_t38_state(p, T38_ENABLED); } else if (t38action == SDP_T38_INITIATE) { if (p->owner && p->lastinvite) { - p->t38.state = T38_PEER_REINVITE; /* T38 Offered in re-invite from remote party */ + change_t38_state(p, T38_PEER_REINVITE); /* T38 Offered in re-invite from remote party */ } else { - p->t38.state = T38_PEER_DIRECT; /* T38 Offered directly from peer in first invite */ + change_t38_state(p, T38_PEER_DIRECT); /* T38 Offered directly from peer in first invite */ } } } else { - p->t38.state = T38_DISABLED; + change_t38_state(p, T38_DISABLED); } - ast_debug(3, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); - /* Now gather all of the codecs that we are asked for: */ ast_rtp_get_current_formats(newaudiortp, &peercapability, &peernoncodeccapability); ast_rtp_get_current_formats(newvideortp, &vpeercapability, &vpeernoncodeccapability); @@ -14384,23 +14470,19 @@ } else { ast_debug(2, "Strange... The other side of the bridge does not have a udptl struct\n"); sip_pvt_lock(bridgepvt); - bridgepvt->t38.state = T38_DISABLED; + change_t38_state(bridgepvt, T38_DISABLED); sip_pvt_unlock(bridgepvt); - ast_debug(1,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->tech->type); - p->t38.state = T38_DISABLED; - ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_DISABLED); } } else { /* Other side is not a SIP channel */ ast_debug(2, "Strange... The other side of the bridge is not a SIP channel\n"); - p->t38.state = T38_DISABLED; - ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_DISABLED); } } if ((p->t38.state == T38_LOCAL_REINVITE) || (p->t38.state == T38_LOCAL_DIRECT)) { /* If there was T38 reinvite and we are supposed to answer with 200 OK than this should set us to T38 negotiated mode */ - p->t38.state = T38_ENABLED; - ast_debug(1, "T38 changed state to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_ENABLED); } if (!req->ignore && p->owner) { @@ -14537,7 +14619,7 @@ terribly wrong since we don't renegotiate codecs, only IP/port . */ - p->t38.state = T38_DISABLED; + change_t38_state(p, T38_DISABLED); /* Try to reset RTP timers */ ast_rtp_set_rtptimers_onhold(p->rtp); ast_log(LOG_ERROR, "Got error on T.38 re-invite. Bad configuration. Peer needs to have T.38 disabled.\n"); @@ -14553,11 +14635,11 @@ /* We tried to send T.38 out in an initial INVITE and the remote side rejected it, right now we can't fall back to audio so totally abort. */ - p->t38.state = T38_DISABLED; /* Try to reset RTP timers */ ast_rtp_set_rtptimers_onhold(p->rtp); ast_log(LOG_ERROR, "Got error on T.38 initial invite. Bailing out.\n"); + change_t38_state(p, T38_DISABLED); /* The dialog is now terminated */ if (p->owner && !req->ignore) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); @@ -16441,9 +16523,8 @@ } else { /* Something is wrong with peers udptl struct */ ast_log(LOG_WARNING, "Strange... The other side of the bridge don't have udptl struct\n"); sip_pvt_lock(bridgepvt); - bridgepvt->t38.state = T38_DISABLED; + change_t38_state(bridgepvt, T38_DISABLED); sip_pvt_unlock(bridgepvt); - ast_debug(2,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->name); if (req->ignore) transmit_response(p, "488 Not acceptable here", req); else @@ -16453,8 +16534,7 @@ } else { /* The other side is already setup for T.38 most likely so we need to acknowledge this too */ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL); - p->t38.state = T38_ENABLED; - ast_debug(1, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_ENABLED); } } else { /* Other side is not a SIP channel */ @@ -16462,8 +16542,7 @@ transmit_response(p, "488 Not acceptable here", req); else transmit_response_reliable(p, "488 Not acceptable here", req); - p->t38.state = T38_DISABLED; - ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_DISABLED); if (!p->lastinvite) /* Only destroy if this is *not* a re-invite */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); @@ -16471,8 +16550,7 @@ } else { /* we are not bridged in a call */ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL); - p->t38.state = T38_ENABLED; - ast_debug(1,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); + change_t38_state(p, T38_DISABLED); } } else if (p->t38.state == T38_DISABLED) { /* Channel doesn't have T38 offered or enabled */ int sendok = TRUE; @@ -20638,10 +20716,8 @@ ast_debug(3, "Responding 200 OK on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port)); else ast_debug(3, "Responding 200 OK on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr)); - pvt->t38.state = T38_ENABLED; - p->t38.state = T38_ENABLED; - ast_debug(2, "T38 changed state to %d on channel %s\n", pvt->t38.state, pvt->owner ? pvt->owner->name : ""); - ast_debug(2, "T38 changed state to %d on channel %s\n", p->t38.state, chan ? chan->name : ""); + change_t38_state(pvt, T38_ENABLED); + change_t38_state(p, T38_ENABLED); transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL); p->lastrtprx = p->lastrtptx = time(NULL); sip_pvt_unlock(p); Index: include/asterisk/channel.h =================================================================== --- include/asterisk/channel.h (revision 100974) +++ include/asterisk/channel.h (working copy) @@ -389,6 +389,14 @@ AST_STATE_MUTE = (1 << 16), /*!< Do not transmit voice data */ }; +enum ast_t38state { + T38_UNAVAILABLE, /*!< T38 is unavailable in this channel (like on Zap ones) or disabled by configuration */ + T38_UNKNOWN, /*!< The channel supports T38 but its current status is unknown */ + T38_NEGOTIATING, /*!< T38 is being negotiated */ + T38_REJECTED, /*!< Remote side has rejected our offer */ + T38_NEGOTIATED, /*!< T38 established */ +}; + /*! \brief Main Channel structure associated with a channel. * This is the side of it mostly used by the pbx and call management. * @@ -1272,10 +1280,10 @@ /*! Checks the value of an option */ /*! - * Query the value of an option, optionally blocking until a reply is received + * Query the value of an option * Works similarly to setoption except only reads the options. */ -struct ast_frame *ast_channel_queryoption(struct ast_channel *channel, int option, void *data, int *datalen, int block); +int ast_channel_queryoption(struct ast_channel *channel, int option, void *data, int *datalen, int block); /*! Checks for HTML support on a channel */ /*! Returns 0 if channel does not support HTML or non-zero if it does */ @@ -1529,6 +1537,16 @@ #endif } +static inline enum ast_t38state ast_channel_get_t38_state(struct ast_channel *chan) +{ + enum ast_t38state state = T38_UNAVAILABLE; + int datalen = sizeof(state); + ast_channel_queryoption(chan, AST_OPTION_T38_STATE, &state, &datalen, 0); + + return state; +} + + #ifdef DO_CRASH #define CRASH do { fprintf(stderr, "!! Forcing immediate crash a-la abort !!\n"); *((int *)0) = 0; } while(0) #else Index: include/asterisk/frame.h =================================================================== --- include/asterisk/frame.h (revision 100974) +++ include/asterisk/frame.h (working copy) @@ -292,6 +292,11 @@ AST_CONTROL_HOLD = 16, /*!< Indicate call is placed on hold */ AST_CONTROL_UNHOLD = 17, /*!< Indicate call is left from hold */ AST_CONTROL_VIDUPDATE = 18, /*!< Indicate video frame update */ + AST_CONTROL_T38_REQUEST_NEGOTIATE = 19, /*!< Request T38 on a channel (voice to fax) */ + AST_CONTROL_T38_REQUEST_TERMINATE = 20, /*!< Terminate T38 on a channel (fax to voice) */ + AST_CONTROL_T38_NEGOTIATED = 21, /*!< T38 negotiated (fax mode) */ + AST_CONTROL_T38_TERMINATED = 22, /*!< T38 terminated (back to voice) */ + AST_CONTROL_T38_REFUSED = 23 /*!< T38 refused for some reason (usually rejected by remote end) */ }; #define AST_SMOOTHER_FLAG_G729 (1 << 0) @@ -340,6 +345,12 @@ /*! Explicitly enable or disable echo cancelation for the given channel */ #define AST_OPTION_ECHOCAN 8 +/* ! + * Read-only. Allows query current status of T38 on the channel. + * data: ast_t38state + */ +#define AST_OPTION_T38_STATE 10 + struct oprmode { struct ast_channel *peer; int mode; Index: main/channel.c =================================================================== --- main/channel.c (revision 100974) +++ main/channel.c (working copy) @@ -4323,23 +4323,22 @@ /*! \brief Sets an option on a channel */ int ast_channel_setoption(struct ast_channel *chan, int option, void *data, int datalen, int block) { - int res; - - if (chan->tech->setoption) { - res = chan->tech->setoption(chan, option, data, datalen); - if (res < 0) - return res; - } else { + if (!chan->tech->setoption) { errno = ENOSYS; return -1; } - if (block) { - /* XXX Implement blocking -- just wait for our option frame reply, discarding - intermediate packets. XXX */ - ast_log(LOG_ERROR, "XXX Blocking not implemented yet XXX\n"); + + return chan->tech->setoption(chan, option, data, datalen); +} + +int ast_channel_queryoption(struct ast_channel *chan, int option, void *data, int *datalen, int block) +{ + if (!chan->tech->queryoption) { + errno = ENOSYS; return -1; } - return 0; + + return chan->tech->queryoption(chan, option, data, datalen); } struct tonepair_def {