From 551dc7de88217169f01624a64c4a20ded378c7d8 Mon Sep 17 00:00:00 2001 From: Liong Sauw Ming Date: Mon, 15 Jul 2013 04:23:31 +0000 Subject: Re #817: Initial work for allowing application to respond to re-INVITE manually. Patches integrated in this fix (with some modifications for adjustment to the current trunk): - sip_inv-on_rx_reinvite.patch Move the place where to call the callback so the callback will still be called when the re-invite contains no SDP - sdp_neg_cancel_remote_offer pjmedia_sdp_neg_cancel_remote_offer() is no longer necessary since pjmedia_sdp_neg_cancel_offer() can already handle that. Only integrate the chart for sdp negotiation doc. - sip_inv-terminate-reinvite-tsx-on-cancel - sip_inv-cancel_sdp_neg_on_sending_negative_reply_to_reinvite - pjsip-allow_cancel_reinvite git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4562 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/include/pjmedia/sdp_neg.h | 12 ++-- pjsip/include/pjsip-ua/sip_inv.h | 47 +++++++++++++- pjsip/src/pjsip-ua/sip_inv.c | 132 ++++++++++++++++++++++++++++++++++---- 3 files changed, 168 insertions(+), 23 deletions(-) diff --git a/pjmedia/include/pjmedia/sdp_neg.h b/pjmedia/include/pjmedia/sdp_neg.h index 0be9e528..3f6eb319 100644 --- a/pjmedia/include/pjmedia/sdp_neg.h +++ b/pjmedia/include/pjmedia/sdp_neg.h @@ -52,15 +52,15 @@ * create_w_local_offer() +-------------+ send_local_offer() * ----------------------->| LOCAL_OFFER |<----------------------- * | +-------------+______ | - * | | \______ cancel() | - * | set_remote_answer() | \______ | - * | V \ | + * | | \_____________ | + * | set_remote_answer() | cancel_offer() \ | + * | V v | * +--+---+ +-----------+ negotiate() +-~----+ * | NULL | | WAIT_NEGO |-------------------->| DONE | * +------+ +-----------+ +------+ - * | A | - * | set_local_answer() | | - * | | | + * | A ______________________^ | + * | set_local_answer() | / cancel_offer() | + * | | / | * | +--------------+ set_remote_offer() | * ----------------------->| REMOTE_OFFER |<---------------------- * create_w_remote_offer() +--------------+ diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h index 3017b8ce..4e5f820e 100644 --- a/pjsip/include/pjsip-ua/sip_inv.h +++ b/pjsip/include/pjsip-ua/sip_inv.h @@ -160,6 +160,31 @@ typedef struct pjsip_inv_callback void (*on_rx_offer)(pjsip_inv_session *inv, const pjmedia_sdp_session *offer); + /** + * This callback is optional, and is called when the invite session has + * received a re-INVITE from the peer. It will be called after + * on_rx_offer() callback and works only for re-INVITEs. It allows more + * fine-grained control over the response to a re-INVITE, e.g. sending + * a provisional response first. Application can return PJ_SUCCESS and + * send a reply using the function #pjsip_inv_initial_answer() or + * #pjsip_inv_answer(), as with the initial INVITE. If application + * returns non-PJ_SUCCESS, it needs to set the SDP answer with + * #pjsip_inv_set_sdp_answer() and the re-INVITE will be answered + * automatically. + * + * @param inv The invite session. + * @param offer Remote offer. + * @param rdata The received re-INVITE request. + * + * @return - PJ_SUCCESS: application will answer the re-INVITE + * manually + * - non-PJ_SUCCESS: answer the re-INVITE automatically + * using the SDP set via #pjsip_inv_set_sdp_answer() + */ + pj_status_t (*on_rx_reinvite)(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer, + pjsip_rx_data *rdata); + /** * This callback is optional, and it is used to ask the application * to create a fresh offer, when the invite session has received @@ -698,9 +723,7 @@ PJ_DECL(pj_status_t) pjsip_inv_initial_answer( pjsip_inv_session *inv, pjsip_tx_data **p_tdata); /** - * Create a response message to the initial INVITE request. This function - * can only be called for the initial INVITE request, as subsequent - * re-INVITE request will be answered automatically. + * Create a response message to an INVITE request. * * @param inv The UAS invite session. * @param st_code The st_code contains the status code to be sent, @@ -797,6 +820,24 @@ PJ_DECL(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, pjsip_tx_data **p_tdata ); +/** + * Create a CANCEL request for an ongoing re-INVITE transaction. If no + * provisional response has been received, the function will not create + * CANCEL request (the function will return PJ_SUCCESS but the \a p_tdata + * will contain NULL) because we cannot send CANCEL before receiving + * provisional response. If then a provisional response is received, + * the invite session will send CANCEL automatically. + * + * @param inv The invite session. + * @param p_tdata Pointer to receive the message to be created. Note + * that it's possible to receive NULL here while the + * function returns PJ_SUCCESS, see the description. + * + * @return PJ_SUCCESS if termination is initiated. + */ +PJ_DECL(pj_status_t) pjsip_inv_cancel_reinvite( pjsip_inv_session *inv, + pjsip_tx_data **p_tdata ); + /** * Create a re-INVITE request. diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index 4f490f92..87aca6fe 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -1835,6 +1835,15 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, if (pjmedia_sdp_neg_get_state(inv->neg) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { + if (mod_inv.cb.on_rx_reinvite && inv->notify && + msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD) + { + /* Do not return failure first, allow the application + * to set the answer in the on_rx_reinvite() callback. + */ + return PJ_SUCCESS; + } return PJ_EINVALIDOP; } @@ -1972,6 +1981,16 @@ static pj_status_t process_answer( pjsip_inv_session *inv, } } + /* Cancel SDP negotiation if this is a negative reply to a re-INVITE */ + if (st_code >= 300 && inv->neg != NULL && + inv->state == PJSIP_INV_STATE_CONFIRMED) + { + pjmedia_sdp_neg_state neg_state; + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER) { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + } return PJ_SUCCESS; } @@ -2052,8 +2071,7 @@ on_return: /* - * Answer initial INVITE - * Re-INVITE will be answered automatically, and will not use this function. + * Answer INVITE request. */ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, int st_code, @@ -2279,6 +2297,62 @@ PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, return PJ_SUCCESS; } +/* + * Cancel re-INVITE transaction. + */ +PJ_DEF(pj_status_t) pjsip_inv_cancel_reinvite( pjsip_inv_session *inv, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + pj_log_push_indent(); + + /* Create appropriate message. */ + switch (inv->state) { + case PJSIP_INV_STATE_CONFIRMED: + /* MUST have the original UAC INVITE transaction */ + PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG); + + /* CANCEL should only be called when we have received a + * provisional response. + */ + if (inv->invite_tsx->status_code < 100) { + inv->cancelling = PJ_TRUE; + inv->pending_cancel = PJ_TRUE; + *p_tdata = NULL; + PJ_LOG(4, (inv->obj_name, "Delaying CANCEL since no " + "provisional response is received yet")); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + status = pjsip_endpt_create_cancel(inv->dlg->endpt, + inv->invite_tsx->last_tx, + &tdata); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + break; + + default: + /* We cannot send CANCEL to a re-INVITE if the INVITE session is + * not confirmed. + */ + pj_log_pop_indent(); + return PJ_EINVALIDOP; + } + + pj_log_pop_indent(); + + *p_tdata = tdata; + return PJ_SUCCESS; +} + /* Following redirection recursion, get next target from the target set and * notify user. * @@ -4082,17 +4156,9 @@ static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e) { /* - * Handle strandled incoming CANCEL. + * Handle strandled incoming CANCEL or CANCEL for re-INVITE */ - pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; - pjsip_tx_data *tdata; - pj_status_t status; - - status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); - if (status != PJ_SUCCESS) return; - - status = pjsip_dlg_send_response(dlg, tsx, tdata); - if (status != PJ_SUCCESS) return; + inv_respond_incoming_cancel(inv, tsx, e); } else if (tsx->role == PJSIP_ROLE_UAS && tsx->state == PJSIP_TSX_STATE_TRYING && @@ -4228,7 +4294,7 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; pjsip_tx_data *tdata; pj_status_t status; - pjsip_rdata_sdp_info *sdp_info; + pjsip_rdata_sdp_info *sdp_info = NULL; pjsip_status_code st_code; /* Check if we have INVITE pending. */ @@ -4301,6 +4367,29 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) /* Process SDP in incoming message. */ status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata); + if (status == PJ_SUCCESS && mod_inv.cb.on_rx_reinvite && + inv->notify) + { + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if ((*mod_inv.cb.on_rx_reinvite) + (inv, sdp_info->sdp, rdata) == PJ_SUCCESS) + { + /* Application will send its own response. + * Our job is done. */ + return; + } + + /* If application lets us answer the re-INVITE, + * application must set the SDP answer with + * #pjsip_inv_set_sdp_answer(). + */ + if (pjmedia_sdp_neg_get_state(inv->neg) != + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) + { + status = PJ_EINVALIDOP; + } + } + if (status != PJ_SUCCESS) { /* Not Acceptable */ @@ -4342,7 +4431,8 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) /* If the INVITE request has SDP body, send answer. * Otherwise generate offer from local active SDP. */ - sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (!sdp_info) + sdp_info = pjsip_rdata_get_sdp_info(rdata); if (sdp_info->sdp != NULL) { status = process_answer(inv, 200, tdata, NULL); } else { @@ -4459,6 +4549,20 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) /* Save pending invite transaction */ inv->invite_tsx = tsx; + } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { + + /* CANCEL the re-INVITE if necessary */ + if (inv->pending_cancel) { + pj_status_t status; + pjsip_tx_data *cancel; + + inv->pending_cancel = PJ_FALSE; + + status = pjsip_inv_cancel_reinvite(inv, &cancel); + if (status == PJ_SUCCESS && cancel) + status = pjsip_inv_send_msg(inv, cancel); + } + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED && tsx->status_code/100 == 2) { -- cgit v1.2.3