Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 94396) +++ channels/chan_sip.c (working copy) @@ -1512,6 +1512,10 @@ (head) = (element)->next; \ } while (0) + +#define SDP_T38_INITIATE 1 +#define SDP_T38_ACCEPT 2 + /*---------------------------- Forward declarations of functions in chan_sip.c */ /*! \note This is added to help splitting up chan_sip.c into several files in coming releases */ @@ -1531,6 +1535,8 @@ 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 int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen); /*--- Transmitting responses and requests */ static int sipsock_read(int *id, int fd, short events, void *ignore); @@ -1598,7 +1604,7 @@ static const char* get_sdp_iterate(int* start, struct sip_request *req, const char *name); static const char *get_sdp(struct sip_request *req, const char *name); static int find_sdp(struct sip_request *req); -static int process_sdp(struct sip_pvt *p, struct sip_request *req); +static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action); static void add_codec_to_sdp(const struct sip_pvt *p, int codec, int sample_rate, struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size); @@ -1867,6 +1873,8 @@ .early_bridge = ast_rtp_early_bridge, .send_text = sip_sendtext, /* called with chan locked */ .func_channel_read = acf_channel_read, + .setoption = sip_setoption, + .queryoption = sip_queryoption, }; /*! \brief This version of the sip channel tech has no send_digit_begin @@ -2702,6 +2710,76 @@ return res; } +static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen) +{ + int res = -1; + int 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; + } + + 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; + } + + *((enum ast_t38state *) data) = state; + + break; + + case AST_OPTION_T38_ACCEPT: + if (*datalen != sizeof(int)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_ACCEPT option. Expected %d, got %d\n", sizeof(int), *datalen); + return -1; + } + + *((int *) data) = 1; + + break; + } + + return res; +} + +static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen) +{ + int res = -1; + + switch (option) { + + case AST_OPTION_T38_ACCEPT: + if (datalen != sizeof(int)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_ACCEPT option. Expected %d, got %d\n", sizeof(int), datalen); + return -1; + } + +// *((int *) data) = 1; +ast_log(LOG_NOTICE, "AST_OPTION_T38_ACCEPT=%d\n", *((int *) data)); + + 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. @@ -5628,7 +5706,7 @@ Return 0 on success, a negative value on errors. Must be called after find_sdp(). */ -static int process_sdp(struct sip_pvt *p, struct sip_request *req) +static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action) { const char *m; /* SDP media offer */ const char *c; @@ -5797,14 +5875,6 @@ ast_verbose("Got T.38 offer in SDP in dialog %s\n", p->callid); udptlportno = x; numberofmediastreams++; - - if (p->owner && p->lastinvite) { - p->t38.state = T38_PEER_REINVITE; /* T38 Offered in re-invite from remote party */ - ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "" ); - } else { - p->t38.state = T38_PEER_DIRECT; /* T38 Offered directly from peer in first invite */ - ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); - } } else ast_log(LOG_WARNING, "Unsupported SDP media type in offer: %s\n", m); if (numberofports > 1) @@ -6113,11 +6183,25 @@ p->t38.capability, 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; + } 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 */ + } else { + p->t38.state = T38_PEER_DIRECT; /* T38 Offered directly from peer in first invite */ + } + } } else { p->t38.state = T38_DISABLED; - ast_debug(3, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : ""); } + 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); @@ -13510,7 +13594,7 @@ } if (find_sdp(req)) { p->invitestate = INV_EARLY_MEDIA; - res = process_sdp(p, req); + res = process_sdp(p, req, 0); if (!req->ignore && p->owner) { /* Queue a progress frame only if we have SDP in 180 or 182 */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); @@ -13525,7 +13609,7 @@ /* Ignore 183 Session progress without SDP */ if (find_sdp(req)) { p->invitestate = INV_EARLY_MEDIA; - res = process_sdp(p, req); + res = process_sdp(p, req, 0); if (!req->ignore && p->owner) { /* Queue a progress frame */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); @@ -13539,7 +13623,7 @@ sip_cancel_destroy(p); p->authtries = 0; if (find_sdp(req)) { - if ((res = process_sdp(p, req)) && !req->ignore) + if ((res = process_sdp(p, req, SDP_T38_ACCEPT)) && !req->ignore) if (!reinvite) /* This 200 OK's SDP is not acceptable, so we need to ack, then hangup */ /* For re-invites, we try to recover */ @@ -14299,7 +14383,7 @@ if (!req->ignore) sip_cancel_destroy(p); if (find_sdp(req)) - process_sdp(p, req); + process_sdp(p, req, 0); if (p->owner) { /* Queue a progress frame */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); @@ -15214,7 +15298,7 @@ ast_clear_flag(&p->flags[0], SIP_OUTGOING); /* This is now an inbound dialog */ /* Handle SDP here if we already have an owner */ if (find_sdp(req)) { - if (process_sdp(p, req)) { + if (process_sdp(p, req, SDP_T38_INITIATE)) { transmit_response(p, "488 Not acceptable here", req); if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); @@ -15255,7 +15339,7 @@ /* We have a succesful authentication, process the SDP portion if there is one */ if (find_sdp(req)) { - if (process_sdp(p, req)) { + if (process_sdp(p, req, SDP_T38_INITIATE)) { /* Unacceptable codecs */ transmit_response_reliable(p, "488 Not acceptable here", req); p->invitestate = INV_COMPLETED; @@ -16748,7 +16832,7 @@ p->pendinginvite = 0; __sip_ack(p, seqno, 1 /* response */, 0); if (find_sdp(req)) { - if (process_sdp(p, req)) + if (process_sdp(p, req, 0)) return -1; } check_pendings(p); @@ -19463,6 +19547,22 @@ AST_CLI_DEFINE(sip_reload, "Reload SIP configuration"), }; +static int t38_request_handler(const struct ast_channel *chan) +{ + struct sip_pvt *p; + if (strcmp(chan->tech->type, "SIP")) { + ast_log(LOG_WARNING, "t38_request_handler on non-SIP channel\n"); + return -1; + } + + p = (struct sip_pvt *) chan->tech_pvt; + + p->t38.state = T38_LOCAL_REINVITE; + transmit_reinvite_with_sdp(p, TRUE); + + return 0; +} + /*! \brief PBX load module - initialization */ static int load_module(void) { @@ -19534,6 +19634,8 @@ /* And start the monitor for the first time */ restart_monitor(); + ast_set_t38_handler(t38_request_handler); + return AST_MODULE_LOAD_SUCCESS; } @@ -19542,6 +19644,8 @@ { struct sip_pvt *p, *pl; struct ast_context *con; + + ast_reset_t38_handler(); /* First, take us out of the channel type list */ ast_channel_unregister(&sip_tech); Index: include/asterisk/channel.h =================================================================== --- include/asterisk/channel.h (revision 94396) +++ include/asterisk/channel.h (working copy) @@ -383,6 +383,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. * @@ -1125,6 +1133,13 @@ struct ast_channel *ast_walk_channel_by_exten_locked(const struct ast_channel *chan, const char *exten, const char *context); + +typedef int (*t38_request_func_t)(const struct ast_channel *chan); + +void ast_reset_t38_handler(void); +void ast_set_t38_handler(t38_request_func_t t38_request_func); +void ast_request_t38(struct ast_channel *chan); + /*! ! \brief Waits for a digit * \param c channel to wait for a digit on * \param ms how many milliseconds to wait @@ -1266,10 +1281,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 */ @@ -1523,6 +1538,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 94396) +++ include/asterisk/frame.h (working copy) @@ -331,6 +331,18 @@ /*! Explicitly enable or disable echo cancelation for the given channel */ #define AST_OPTION_ECHOCAN 8 +/* ! + * Read/Write. When set, indicates that channel driver should accept T38 from remote side + * data: int + * value: nonzero - accept T38 + */ +#define AST_OPTION_T38_ACCEPT 10 +/* ! + * Read-only. Allows query current status of T38 on the channel. + * data: ast_t38state + */ +#define AST_OPTION_T38_STATE 11 + struct oprmode { struct ast_channel *peer; int mode; Index: main/channel.c =================================================================== --- main/channel.c (revision 94396) +++ main/channel.c (working copy) @@ -4298,23 +4298,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 { @@ -4827,3 +4826,21 @@ snprintf(buf, sizeof(buf), "%d", num); return ast_say_digit_str_full(chan, buf, ints, lang, audiofd, ctrlfd); } + +static t38_request_func_t ast_t38_request_func = NULL; + +void ast_reset_t38_handler() +{ + ast_t38_request_func = NULL; +} + +void ast_set_t38_handler(t38_request_func_t t38_request_func) +{ + ast_t38_request_func = t38_request_func; +} + +void ast_request_t38(struct ast_channel *chan) +{ + if (ast_t38_request_func) + ast_t38_request_func(chan); +}