diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 51a53a7eac..d2042d6782 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -1080,6 +1080,63 @@ typedef struct pjsua_call_setting } pjsua_call_setting; +/** + * This will contain the information passed from the callback + * \a pjsua_on_rejected_incoming_call_cb. + */ +typedef struct pjsua_on_rejected_incoming_call_param { + /** + * The incoming call id. This will be set to PJSUA_INVALID_ID when there is + * no available call slot at the time. + */ + pjsua_call_id call_id; + + /** + * Local URI. + */ + pj_str_t local_info; + + /** + * Remote URI. + */ + pj_str_t remote_info; + + /** + * Rejection code. + */ + int st_code; + + /** + * Rejection text. + */ + pj_str_t st_text; + + /** + * The original INVITE message, if it's not available this will be set + * to NULL. + */ + pjsip_rx_data *rdata; + + /** + * Internal. + */ + struct { + char local_info[PJSIP_MAX_URL_SIZE]; + char remote_info[PJSIP_MAX_URL_SIZE]; + } buf_; + +} pjsua_on_rejected_incoming_call_param; + +/** + * Type of callback to be called when incoming call is rejected. + * + * @param param The rejected call information. + * + */ +typedef void (*pjsua_on_rejected_incoming_call_cb)( + const pjsua_on_rejected_incoming_call_param *param); + + /** * This structure describes application callback to receive various event * notification from PJSUA-API. All of these callbacks are OPTIONAL, @@ -1967,6 +2024,23 @@ typedef struct pjsua_callback */ void (*on_media_event)(pjmedia_event *event); + /** + * This callback will be invoked when the library implicitly rejects + * an incoming call. + * + * In addition to being declined explicitly using the + * #pjsua_call_answer()/#pjsua_call_answer2() method, + * the library may also automatically reject the incoming call due + * to different scenarios, e.g: + * - no available call slot. + * - no available account to handle the call. + * - when an incoming INVITE is received with, for instance, a message + * containing invalid SDP. + * + * See also #pjsua_on_rejected_incoming_call_cb. + */ + pjsua_on_rejected_incoming_call_cb on_rejected_incoming_call; + } pjsua_callback; diff --git a/pjsip/include/pjsua2/endpoint.hpp b/pjsip/include/pjsua2/endpoint.hpp index 7cb3b37bc8..a2d946e0d5 100644 --- a/pjsip/include/pjsua2/endpoint.hpp +++ b/pjsip/include/pjsua2/endpoint.hpp @@ -436,6 +436,43 @@ struct OnMediaEventParam MediaEvent ev; }; +/** + * Parameter of Endpoint::onRejectedIncomingCall() callback. + */ +struct OnRejectedIncomingCallParam +{ + /** + * The incoming call id. This will be set to PJSUA_INVALID_ID when there is + * no available call slot at the time. + */ + pjsua_call_id callId; + + /** + * Local URI. + */ + std::string localInfo; + + /** + * Remote URI. + */ + std::string remoteInfo; + + /** + * Rejection code. + */ + int statusCode; + + /** + * Rejection text. + */ + std::string reason; + + /** + * The original INVITE message, on some cases it is not available. + */ + SipRxData rdata; +}; + /** * This structure describes authentication challenge used in Proxy-Authenticate * or WWW-Authenticate for digest authentication scheme. @@ -1898,6 +1935,23 @@ class Endpoint */ virtual pj_status_t onCredAuth(OnCredAuthParam &prm); + /** + * This callback will be invoked when the library implicitly rejects + * an incoming call. + * + * In addition to being declined explicitly using the Call::answer() + * method, the library may also automatically reject the incoming call + * due to different scenarios, e.g: + * - no available call slot. + * - no available account to handle the call. + * - when an incoming INVITE is received with, for instance, a message + * containing invalid SDP. + * + * @param prm Callback parameters. + */ + virtual void onRejectedIncomingCall(OnRejectedIncomingCallParam &prm) + { PJ_UNUSED_ARG(prm); } + private: static Endpoint *instance_; // static instance LogWriter *writer; // Custom writer, if any @@ -2078,6 +2132,10 @@ class Endpoint const pjsip_cred_info *cred, const pj_str_t *method, pjsip_digest_credential *auth); + + static void on_rejected_incoming_call( + const pjsua_on_rejected_incoming_call_param *param); + friend class Account; diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 609111e2a9..9cb7ae37eb 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -1352,6 +1352,59 @@ static pj_status_t verify_request(const pjsua_call *call, return status; } +static void rejected_incoming_call_cb(pjsua_call_id call_id, + pjsip_rx_data *rdata, + pjsip_tx_data *tdata, + int st_code, + pj_str_t *st_text) +{ + pjsip_from_hdr *from_hdr = NULL; + pjsip_to_hdr *to_hdr = NULL; + pjsip_sip_uri *uri; + pjsua_on_rejected_incoming_call_param param; + + if (!pjsua_var.ua_cfg.cb.on_rejected_incoming_call) + return; + + pj_bzero(¶m, sizeof(pjsua_on_rejected_incoming_call_param)); + param.call_id = call_id; + param.st_code = st_code; + if (rdata) { + param.rdata = rdata; + to_hdr = rdata->msg_info.to; + from_hdr = rdata->msg_info.from; + } else if (tdata) { + pjsip_msg *msg = tdata->msg; + to_hdr = PJSIP_MSG_TO_HDR(msg); + from_hdr = PJSIP_MSG_FROM_HDR(msg); + } + + if (to_hdr) { + param.local_info.ptr = param.buf_.local_info; + uri = (pjsip_sip_uri*)pjsip_uri_get_uri(to_hdr->uri); + param.local_info.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + uri, + param.buf_.local_info, + sizeof(param.buf_.local_info)); + } + if (from_hdr) { + param.remote_info.ptr = param.buf_.remote_info; + uri = (pjsip_sip_uri*)pjsip_uri_get_uri(from_hdr->uri); + param.remote_info.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + uri, + param.buf_.remote_info, + sizeof(param.buf_.remote_info)); + } + + if (st_text && st_text->slen) { + pj_strassign(¶m.st_text, st_text); + } else { + param.st_text = *pjsip_get_status_text(st_code); + } + + pjsua_var.ua_cfg.cb.on_rejected_incoming_call(¶m); +} + /* Incoming call callback when media transport creation is completed. */ static pj_status_t on_incoming_call_med_tp_complete2(pjsua_call_id call_id, @@ -1406,15 +1459,21 @@ on_incoming_call_med_tp_complete2(pjsua_call_id call_id, */ if (call->inv->state > PJSIP_INV_STATE_NULL) { pj_status_t status_ = PJ_SUCCESS; + pj_str_t reason = pj_str(""); if (response == NULL) { - pj_str_t reason = pj_str("Failed creating media transport"); + reason = pj_str("Failed creating media transport"); status_ = pjsip_inv_end_session(call->inv, err_code, &reason, &response); } if (status_ == PJ_SUCCESS && response) status_ = pjsip_inv_send_msg(call->inv, response); + + if ((err_code / 100 != 1) && (err_code / 100 != 2)) { + rejected_incoming_call_cb(call->index, rdata, response, + err_code, &reason); + } } pjsua_media_channel_deinit(call->index); } @@ -1453,7 +1512,6 @@ on_incoming_call_med_tp_complete(pjsua_call_id call_id, return on_incoming_call_med_tp_complete2(call_id, info, NULL, NULL, NULL); } - /** * Handle incoming INVITE request. * Called by pjsua_core.c @@ -1470,11 +1528,13 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_inv_session *inv = NULL; int acc_id; pjsua_call *call = NULL; - int call_id = -1; + int call_id = PJSUA_INVALID_ID; int sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; pjmedia_sdp_session *offer=NULL; pj_bool_t should_dec_dlg = PJ_FALSE; pjsip_tpselector tp_sel; + pj_str_t st_reason = pj_str(""); + int ret_st_code = 0; pj_status_t status; /* Don't want to handle anything but INVITE */ @@ -1504,9 +1564,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) call_id = alloc_call_id(); if (call_id == PJSUA_INVALID_ID) { - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, - PJSIP_SC_BUSY_HERE, NULL, - NULL, NULL); + ret_st_code = PJSIP_SC_BUSY_HERE; + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, ret_st_code, + NULL, NULL, NULL); PJ_LOG(2,(THIS_FILE, "Unable to accept incoming call (too many calls)")); goto on_return; @@ -1534,16 +1594,17 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ if (response) { pjsip_response_addr res_addr; + ret_st_code = response->msg->line.status.code; pjsip_get_response_addr(response->pool, rdata, &res_addr); - status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, - NULL, NULL); + status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, + response, NULL, NULL); if (status != PJ_SUCCESS) pjsip_tx_data_dec_ref(response); } else { - + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, - NULL, NULL); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, ret_st_code, + NULL, NULL, NULL); } goto on_return; @@ -1597,6 +1658,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, st_code, &st_text, NULL, NULL, NULL); + ret_st_code = st_code; goto on_return; } @@ -1625,8 +1687,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) } else { acc_id = call->acc_id = pjsua_acc_find_for_incoming(rdata); if (acc_id == PJSUA_INVALID_ID) { - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, - PJSIP_SC_TEMPORARILY_UNAVAILABLE, + ret_st_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE; + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, ret_st_code, NULL, NULL, NULL); PJ_LOG(2,(THIS_FILE, @@ -1681,13 +1743,13 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) acc->values[acc->count++] = pj_str("application/sdp"); pj_list_init(&hdr_list); pj_list_push_back(&hdr_list, acc); + ret_st_code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; - pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, - PJSIP_SC_UNSUPPORTED_MEDIA_TYPE, + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, ret_st_code, NULL, &hdr_list, NULL, NULL); } else { - const pj_str_t reason = pj_str("Bad SDP"); pjsip_warning_hdr *w; + st_reason = pj_str("Bad SDP"); pjsua_perror(THIS_FILE, "Bad SDP in incoming INVITE", status); @@ -1697,9 +1759,10 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) status); pj_list_init(&hdr_list); pj_list_push_back(&hdr_list, w); + ret_st_code = PJSIP_SC_BAD_REQUEST; - pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, - &reason, &hdr_list, NULL, NULL); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, ret_st_code, + &st_reason, &hdr_list, NULL, NULL); } goto on_return; } @@ -1708,9 +1771,11 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) * checks will be done in pjsip_inv_verify_request2() below. */ if ((offer) && (offer->media_count==0)) { - const pj_str_t reason = pj_str("Missing media in SDP"); - pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, &reason, - NULL, NULL, NULL); + st_reason = pj_str("Missing media in SDP"); + ret_st_code = PJSIP_SC_BAD_REQUEST; + + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, ret_st_code, + &st_reason, NULL, NULL, NULL); goto on_return; } @@ -1745,16 +1810,18 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ if (response) { pjsip_response_addr res_addr; + ret_st_code = response->msg->line.status.code; pjsip_get_response_addr(response->pool, rdata, &res_addr); - status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, - NULL, NULL); + status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, + response, NULL, NULL); if (status != PJ_SUCCESS) pjsip_tx_data_dec_ref(response); } else { /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 500, NULL, - NULL, NULL, NULL); + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, ret_st_code, + NULL, NULL, NULL, NULL); } goto on_return; @@ -1767,10 +1834,11 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact, acc_id, rdata); if (status != PJ_SUCCESS) { + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; pjsua_perror(THIS_FILE, "Unable to generate Contact header", status); - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, - NULL, NULL); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, ret_st_code, + NULL, NULL, NULL); goto on_return; } } @@ -1779,8 +1847,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) status = pjsip_dlg_create_uas_and_inc_lock( pjsip_ua_instance(), rdata, &contact, &dlg); if (status != PJ_SUCCESS) { - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, - NULL, NULL); + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, ret_st_code, + NULL, NULL, NULL); goto on_return; } @@ -1859,8 +1928,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) status); pj_list_init(&hdr_list); pj_list_push_back(&hdr_list, w); - - pjsip_dlg_respond(dlg, rdata, 500, NULL, &hdr_list, NULL); + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + pjsip_dlg_respond(dlg, rdata, ret_st_code, NULL, &hdr_list, NULL); /* Can't terminate dialog because transaction is in progress. pjsip_dlg_terminate(dlg); @@ -1901,8 +1970,12 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_dlg_inc_lock(dlg); if (response) { + ret_st_code = response->msg->line.status.code; + pjsip_dlg_send_response(dlg, call->inv->invite_tsx, response); } else { + ret_st_code = sip_err_code; + pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL); } @@ -1944,10 +2017,14 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_dlg_inc_lock(dlg); if (response) { + ret_st_code = response->msg->line.status.code; + pjsip_dlg_send_response(dlg, call->inv->invite_tsx, response); } else { + ret_st_code = sip_err_code; + pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL); } @@ -1965,6 +2042,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsua_perror(THIS_FILE, "Error initializing media channel", status); pjsip_dlg_inc_lock(dlg); + ret_st_code = sip_err_code; pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL); if (call->inv->dlg) { pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE); @@ -1994,8 +2072,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) &pjsua_var.acc[acc_id].cfg.timer_setting); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Session Timer init failed", status); - pjsip_dlg_respond(dlg, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); - pjsip_inv_terminate(inv, PJSIP_SC_INTERNAL_SERVER_ERROR, PJ_FALSE); + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + pjsip_dlg_respond(dlg, rdata, ret_st_code, NULL, NULL, NULL); + pjsip_inv_terminate(inv, ret_st_code, PJ_FALSE); pjsua_media_channel_deinit(call->index); call->inv = NULL; @@ -2030,12 +2109,14 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) if (response == NULL) { pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE", status); - pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL); - pjsip_inv_terminate(inv, 500, PJ_FALSE); + ret_st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + pjsip_dlg_respond(dlg, rdata, ret_st_code, NULL, NULL, NULL); + pjsip_inv_terminate(inv, ret_st_code, PJ_FALSE); } else { + ret_st_code = response->msg->line.status.code; + pjsip_inv_send_msg(inv, response); - pjsip_inv_terminate(inv, response->msg->line.status.code, - PJ_FALSE); + pjsip_inv_terminate(inv, ret_st_code, PJ_FALSE); } pjsua_media_channel_deinit(call->index); call->inv = NULL; @@ -2122,6 +2203,13 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_rx_data_free_cloned(call->incoming_data); call->incoming_data = NULL; } + + if ((ret_st_code != 0) && (ret_st_code / 100 != 1) && + (ret_st_code / 100 != 2)) + { + rejected_incoming_call_cb(call_id, rdata, NULL, ret_st_code, + &st_reason); + } pj_log_pop_indent(); PJSUA_UNLOCK(); diff --git a/pjsip/src/pjsua2/endpoint.cpp b/pjsip/src/pjsua2/endpoint.cpp index 9175e52800..f404d01939 100644 --- a/pjsip/src/pjsua2/endpoint.cpp +++ b/pjsip/src/pjsua2/endpoint.cpp @@ -1962,6 +1962,7 @@ void Endpoint::libInit(const EpConfig &prmEpConfig) PJSUA2_THROW(Error) ua_cfg.cb.on_create_media_transport = &Endpoint::on_create_media_transport; ua_cfg.cb.on_stun_resolution_complete = &Endpoint::stun_resolve_cb; + ua_cfg.cb.on_rejected_incoming_call = &Endpoint::on_rejected_incoming_call; /* Init! */ PJSUA2_CHECK_EXPR( pjsua_init(&ua_cfg, &log_cfg, &med_cfg) ); @@ -2687,3 +2688,18 @@ pj_status_t Endpoint::on_auth_create_aka_response_callback(pj_pool_t *pool, #endif return status; } + +void Endpoint::on_rejected_incoming_call( + const pjsua_on_rejected_incoming_call_param *param) +{ + OnRejectedIncomingCallParam prm; + prm.callId = param->call_id; + prm.localInfo = pj2Str(param->local_info); + prm.remoteInfo = pj2Str(param->remote_info); + prm.statusCode = param->st_code; + prm.reason = pj2Str(param->st_text); + if (param->rdata) + prm.rdata.fromPj(*param->rdata); + + Endpoint::instance().onRejectedIncomingCall(prm); +}