diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 43ed8648d2f7915e4cd1e6e292f924ba34cb520c..54e233aa861c331e83f5341473e32e4767ebc5cd 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1689,6 +1689,7 @@ static int sip_transfer(struct ast_channel *ast, const char *dest); 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 const char *sip_get_callid(struct ast_channel *chan); static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin); @@ -2009,6 +2010,7 @@ static int sip_handle_t38_reinvite(struct ast_channel *chan, struct sip_pvt *pvt 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); @@ -2050,6 +2052,7 @@ static const struct ast_channel_tech sip_tech = { .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, }; @@ -3083,6 +3086,53 @@ static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittyp return res; } +/*! \brief Query an option on a SIP dialog */ +static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen) +{ + int res = -1; + enum ast_t38_state state = T38_STATE_UNAVAILABLE; + struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt; + + switch (option) { + case AST_OPTION_T38_STATE: + /* Make sure we got an ast_t38_state enum passed in */ + if (*datalen != sizeof(enum ast_t38_state)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen); + return -1; + } + + sip_pvt_lock(p); + + /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */ + if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT)) { + switch (p->t38.state) { + case T38_LOCAL_DIRECT: + case T38_LOCAL_REINVITE: + case T38_PEER_DIRECT: + case T38_PEER_REINVITE: + state = T38_STATE_NEGOTIATING; + break; + case T38_ENABLED: + state = T38_STATE_NEGOTIATED; + break; + default: + state = T38_STATE_UNKNOWN; + } + } + + sip_pvt_unlock(p); + + *((enum ast_t38_state *) data) = state; + res = 0; + + break; + default: + 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. @@ -3773,6 +3823,37 @@ static void do_setnat(struct sip_pvt *p, int natflags) } } +/*! \brief Change the T38 state on a SIP dialog */ +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 = 0; + + /* Don't bother changing if we are already in the state wanted */ + 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 : "<none>"); + + /* If no channel was provided we can't send off a control frame */ + if (!chan) + return; + + /* Given the state requested and old state determine what control frame we want to queue up */ + 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; + + /* Woot we got a message, create a control frame and send it on! */ + 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) { @@ -4741,8 +4822,7 @@ static int sip_answer(struct ast_channel *ast) 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); @@ -5027,6 +5107,26 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data } else res = -1; break; + case AST_CONTROL_T38: /* T38 control frame */ + if (datalen != sizeof(enum ast_control_t38)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_T38. Expected %d, got %d\n", (int)sizeof(enum ast_control_t38), (int)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: + break; + } + } + break; case -1: res = -1; break; @@ -5459,9 +5559,8 @@ static struct ast_frame *sip_read(struct ast_channel *ast) 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); @@ -6627,24 +6726,21 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action 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 : "<none>"); - /* 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); @@ -14438,23 +14534,19 @@ static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, stru } 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 : "<none>"); + 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 : "<none>"); + 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 : "<none>"); + change_t38_state(p, T38_ENABLED); } if (!req->ignore && p->owner) { @@ -14591,7 +14683,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, stru 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"); @@ -14607,11 +14699,11 @@ static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, stru /* 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); @@ -16497,9 +16589,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int } 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 @@ -16509,8 +16600,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int } 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 : "<none>"); + change_t38_state(p, T38_ENABLED); } } else { /* Other side is not a SIP channel */ @@ -16518,8 +16608,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int 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 : "<none>"); + change_t38_state(p, T38_DISABLED); if (!p->lastinvite) /* Only destroy if this is *not* a re-invite */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); @@ -16527,8 +16616,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int } 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 : "<none>"); + change_t38_state(p, T38_ENABLED); } } else if (p->t38.state == T38_DISABLED) { /* Channel doesn't have T38 offered or enabled */ int sendok = TRUE; @@ -20711,10 +20799,8 @@ static int sip_handle_t38_reinvite(struct ast_channel *chan, struct sip_pvt *pvt 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 : "<none>"); - ast_debug(2, "T38 changed state to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>"); + 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); diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 0faddb870afe956f33ca683d45500e8804511b10..e5fd5ba81e8c4ce82273dbfbc275a332902fb3a9 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -389,6 +389,17 @@ enum ast_channel_state { AST_STATE_MUTE = (1 << 16), /*!< Do not transmit voice data */ }; +/*! + * \brief Possible T38 states on channels + */ +enum ast_t38_state { + T38_STATE_UNAVAILABLE, /*!< T38 is unavailable on this channel or disabled by configuration */ + T38_STATE_UNKNOWN, /*!< The channel supports T38 but the current status is unknown */ + T38_STATE_NEGOTIATING, /*!< T38 is being negotiated */ + T38_STATE_REJECTED, /*!< Remote side has rejected our offer */ + T38_STATE_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. * @@ -1300,10 +1311,10 @@ int ast_best_codec(int fmts); /*! 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 */ @@ -1557,6 +1568,18 @@ static inline int ast_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, #endif } +/*! \brief Retrieves the current T38 state of a channel */ +static inline enum ast_t38_state ast_channel_get_t38_state(struct ast_channel *chan) +{ + enum ast_t38_state state = T38_STATE_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 diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 3983c84cf53f95ed9dec3491f29ae6fba1e8b241..012f7a95292c48abd7938fa7717b35030a2e3c7d 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -292,6 +292,15 @@ enum ast_control_frame_type { 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) @@ -340,6 +349,12 @@ enum ast_control_frame_type { /*! 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; diff --git a/main/channel.c b/main/channel.c index bbfcfdd4a4266ec6c8991f8220666f1871de2506..197cf1e71a04cc883b8dd6bc5df1dafe5f5d1dae 100644 --- a/main/channel.c +++ b/main/channel.c @@ -4481,23 +4481,28 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha /*! \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 */ + + if (block) 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; + + if (block) + ast_log(LOG_ERROR, "XXX Blocking not implemented yet XXX\n"); + + return chan->tech->queryoption(chan, option, data, datalen); } struct tonepair_def { diff --git a/main/frame.c b/main/frame.c index ff182cc0507dea963a918fa9a00319461808e3c6..304bd2716ade0fca9e29c34a1ce84a5acf0d7b4e 100644 --- a/main/frame.c +++ b/main/frame.c @@ -710,6 +710,7 @@ void ast_frame_dump(const char *name, struct ast_frame *f, char *prefix) char cn[60]; char cp[40]; char cmn[40]; + const char *message = "Unknown"; if (!name) name = noname; @@ -786,6 +787,24 @@ void ast_frame_dump(const char *name, struct ast_frame *f, char *prefix) case AST_CONTROL_UNHOLD: strcpy(subclass, "Unhold"); break; + case AST_CONTROL_T38: + if (f->datalen != sizeof(enum ast_control_t38)) { + message = "Invalid"; + } else { + enum ast_control_t38 state = *((enum ast_control_t38 *) f->data); + if (state == AST_T38_REQUEST_NEGOTIATE) + message = "Negotiation Requested"; + else if (state == AST_T38_REQUEST_TERMINATE) + message = "Negotiation Request Terminated"; + else if (state == AST_T38_NEGOTIATED) + message = "Negotiated"; + else if (state == AST_T38_TERMINATED) + message = "Terminated"; + else if (state == AST_T38_REFUSED) + message = "Refused"; + } + snprintf(subclass, sizeof(subclass), "T38/%s", message); + break; case -1: strcpy(subclass, "Stop generators"); break;