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; + enum ast_control_t38 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_T38_NEGOTIATED; + else if (state == T38_DISABLED && old == T38_ENABLED) + message = AST_T38_TERMINATED; + else if (state == T38_DISABLED && old == T38_LOCAL_REINVITE) + message = AST_T38_REFUSED; + + if (message) { + ast_queue_control_data(chan, AST_CONTROL_T38, &message, sizeof(message)); + } + } + +} + /*! \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,30 @@ } else res = -1; break; + case AST_CONTROL_T38: /* T38 request */ + if (datalen != sizeof(enum ast_control_t38)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_T38. Expected %d, got %d\n", sizeof(enum ast_control_t38), datalen); + } else { + + switch (*((enum ast_control_t38 *) data)) { + case AST_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_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (p->t38.state == T38_ENABLED) { + transmit_reinvite_with_sdp(p, FALSE, FALSE); + } + break; + default: + /* Do nothing. Present here just to avoid compiler warnings about not handled enum values */ + break; + } + } + res = -1; + break; case -1: res = -1; break; @@ -5440,9 +5543,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 +6710,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 +14483,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 +14632,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 +14648,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 +16536,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 +16547,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 +16555,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 +16563,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_ENABLED); } } else if (p->t38.state == T38_DISABLED) { /* Channel doesn't have T38 offered or enabled */ int sendok = TRUE; @@ -20638,10 +20729,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,8 +292,17 @@ 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 = 19 /*!< T38 state change request/notification */ }; +enum ast_control_t38 { + AST_T38_REQUEST_NEGOTIATE = 1, /*!< Request T38 on a channel (voice to fax) */ + AST_T38_REQUEST_TERMINATE, /*!< Terminate T38 on a channel (fax to voice) */ + AST_T38_NEGOTIATED, /*!< T38 negotiated (fax mode) */ + AST_T38_TERMINATED, /*!< T38 terminated (back to voice) */ + AST_T38_REFUSED /*!< T38 refused for some reason (usually rejected by remote end) */ +}; + #define AST_SMOOTHER_FLAG_G729 (1 << 0) #define AST_SMOOTHER_FLAG_BE (1 << 1) @@ -340,6 +349,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 { Index: main/frame.c =================================================================== --- main/frame.c (revision 100974) +++ main/frame.c (working copy) @@ -710,6 +710,7 @@ char cn[60]; char cp[40]; char cmn[40]; + char *message; if (!name) name = noname; @@ -780,6 +781,21 @@ case AST_CONTROL_RADIO_UNKEY: strcpy(subclass, "Unkey Radio"); break; + case AST_CONTROL_T38: + if (f->datalen != sizeof(enum ast_control_t38)) { + message = "invalid"; + } else { + switch (*((enum ast_control_t38 *) f->data)) { + case AST_T38_REQUEST_NEGOTIATE: message = "AST_T38_REQUEST_NEGOTIATE"; break; + case AST_T38_REQUEST_TERMINATE: message = "AST_T38_REQUEST_TERMINATE"; break; + case AST_T38_NEGOTIATED: message = "AST_T38_NEGOTIATED"; break; + case AST_T38_TERMINATED: message = "AST_T38_TERMINATED"; break; + case AST_T38_REFUSED: message = "AST_T38_REFUSED"; break; + default: message = "unknown"; break; + } + } + snprintf(subclass, sizeof(subclass), "T38/%s", message); + break; case -1: strcpy(subclass, "Stop generators"); break;