diff options
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 52 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_timer.c | 98 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 19 | ||||
-rw-r--r-- | tests/pjsua/scripts-sipp/uas-timer-reinvite.xml | 108 | ||||
-rw-r--r-- | tests/pjsua/scripts-sipp/uas-timer-update.xml | 126 |
5 files changed, 352 insertions, 51 deletions
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index ecfebec3..c58a92f7 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -2393,7 +2393,7 @@ PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, pj_status_t status = PJ_SUCCESS; /* Verify arguments. */ - PJ_ASSERT_RETURN(inv && p_tdata && offer, PJ_EINVAL); + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); /* Dialog must have been established */ PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED, @@ -2406,24 +2406,26 @@ PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, /* Lock dialog. */ pjsip_dlg_inc_lock(inv->dlg); - /* Process offer */ - if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) { - PJ_LOG(4,(inv->dlg->obj_name, - "Invalid SDP offer/answer state for UPDATE")); - status = PJ_EINVALIDOP; - goto on_error; - } + /* Process offer, if any */ + if (offer) { + if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) { + PJ_LOG(4,(inv->dlg->obj_name, + "Invalid SDP offer/answer state for UPDATE")); + status = PJ_EINVALIDOP; + goto on_error; + } - /* Notify negotiator about the new offer. This will fix the offer - * with correct SDP origin. - */ - status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg, - offer); - if (status != PJ_SUCCESS) - goto on_error; + /* Notify negotiator about the new offer. This will fix the offer + * with correct SDP origin. + */ + status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg, + offer); + if (status != PJ_SUCCESS) + goto on_error; - /* Retrieve the "fixed" offer from negotiator */ - pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + /* Retrieve the "fixed" offer from negotiator */ + pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + } /* Update Contact if required */ if (new_contact) { @@ -2449,8 +2451,10 @@ PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, goto on_error; /* Attach SDP body */ - sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer); - pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body); + if (offer) { + sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer); + pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body); + } /* Unlock dialog. */ pjsip_dlg_dec_lock(inv->dlg); @@ -2879,6 +2883,16 @@ static void inv_handle_update_response( pjsip_inv_session *inv, /* Get/attach invite session's transaction data */ else { + /* Session-Timer needs to see any error responses, to determine + * whether peer supports UPDATE with empty body. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->role == PJSIP_ROLE_UAC) + { + status = handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_FALSE); + } + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; if (tsx_inv_data == NULL) { tsx_inv_data=PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data); diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c index 34676e0d..bb97c3d0 100644 --- a/pjsip/src/pjsip-ua/sip_timer.c +++ b/pjsip/src/pjsip-ua/sip_timer.c @@ -59,6 +59,7 @@ struct pjsip_timer pj_timer_entry timer; /**< Timer entry */ pj_bool_t use_update; /**< Use UPDATE method to refresh the session */ + pj_bool_t with_sdp; /**< SDP in UPDATE? */ pjsip_role_e role; /**< Role in last INVITE/ UPDATE transaction. */ @@ -321,7 +322,7 @@ static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata, * the session if UA is the refresher, otherwise it is time to end * the session. */ -void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +static void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data; pjsip_tx_data *tdata = NULL; @@ -330,38 +331,55 @@ void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) pj_assert(inv); - PJ_UNUSED_ARG(timer_heap); - - /* When there is a pending INVITE transaction, delay/reschedule this timer - * for five seconds to cover the case that pending INVITE fails and the - * previous session is still active. If the pending INVITE is successful, - * timer state will be updated, i.e: restarted or stopped. - */ - if (inv->invite_tsx != NULL) { - pj_time_val delay = {5}; + inv->timer->timer.id = 0; - inv->timer->timer.id = 1; - pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, &delay); - return; - } + PJ_UNUSED_ARG(timer_heap); /* Lock dialog. */ pjsip_dlg_inc_lock(inv->dlg); /* Check our role */ - as_refresher = + as_refresher = (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) || (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS); /* Do action based on role, refresher or refreshee */ if (as_refresher) { - pj_time_val now; + /* As refresher, reshedule the refresh request on the following: + * - msut not send re-INVITE if another INVITE or SDP negotiation + * is in progress. + * - must not send UPDATE with SDP if SDP negotiation is in progress + */ + pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if ( (!inv->timer->use_update && ( + inv->invite_tsx != NULL || + neg_state != PJMEDIA_SDP_NEG_STATE_DONE) + ) + || + (inv->timer->use_update && inv->timer->with_sdp && + neg_state != PJMEDIA_SDP_NEG_STATE_DONE + ) + ) + { + pj_time_val delay = {1, 0}; + + inv->timer->timer.id = 1; + pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, + &delay); + pjsip_dlg_dec_lock(inv->dlg); + return; + } + /* Refresher, refresh the session */ if (inv->timer->use_update) { - /* Create UPDATE request without offer */ - status = pjsip_inv_update(inv, NULL, NULL, &tdata); + const pjmedia_sdp_session *offer = NULL; + + if (inv->timer->with_sdp) { + pjmedia_sdp_neg_get_active_local(inv->neg, &offer); + } + status = pjsip_inv_update(inv, NULL, offer, &tdata); } else { /* Create re-INVITE without modifying session */ pjsip_msg_body *body; @@ -384,8 +402,8 @@ void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) } pj_gettimeofday(&now); - PJ_LOG(4, (inv->pool->obj_name, - "Refresh session after %ds (expiration period=%ds)", + PJ_LOG(4, (inv->pool->obj_name, + "Refreshing session after %ds (expiration period=%ds)", (now.sec-inv->timer->last_refresh.sec), inv->timer->setting.sess_expires)); } else { @@ -414,23 +432,19 @@ void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) /* Print error message, if any */ if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - if (tdata) pjsip_tx_data_dec_ref(tdata); - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(2, (inv->pool->obj_name, "Session timer fails in %s session, " - "err code=%d (%s)", - (as_refresher? "refreshing" : - "terminating"), - status, errmsg)); + PJ_PERROR(2, (inv->pool->obj_name, status, + "Error in %s session timer", + (as_refresher? "refreshing" : "terminating"))); } } /* Start Session Timers */ static void start_timer(pjsip_inv_session *inv) { + const pj_str_t UPDATE = { "UPDATE", 6 }; pjsip_timer *timer = inv->timer; pj_time_val delay = {0}; @@ -438,6 +452,14 @@ static void start_timer(pjsip_inv_session *inv) stop_timer(inv); + inv->timer->use_update = + (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, + &UPDATE) == PJSIP_DIALOG_CAP_SUPPORTED); + if (!inv->timer->use_update) { + /* INVITE always needs SDP */ + inv->timer->with_sdp = PJ_TRUE; + } + pj_timer_entry_init(&timer->timer, 1, /* id */ inv, /* user data */ @@ -837,14 +859,30 @@ PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv, */ inv->timer->refresher = TR_UAC; - PJ_TODO(CHECK_IF_REMOTE_SUPPORT_UPDATE); - /* Remember our role in this transaction */ inv->timer->role = PJSIP_ROLE_UAC; /* Finally, set active flag and start the Session Timers */ inv->timer->active = PJ_TRUE; start_timer(inv); + + } else if (pjsip_method_cmp(&rdata->msg_info.cseq->method, + &pjsip_update_method) == 0 && + msg->line.status.code >= 400 && msg->line.status.code < 600) + { + /* This is to handle error response to previous UPDATE that was + * sent without SDP. In this case, retry sending UPDATE but + * with SDP this time. + * Note: the additional expressions are to check that the + * UPDATE was really the one sent by us, not by other + * call components (e.g. to change codec) + */ + if (inv->timer->timer.id == 0 && inv->timer->use_update && + inv->timer->with_sdp == PJ_FALSE) + { + inv->timer->with_sdp = PJ_TRUE; + timer_cb(NULL, &inv->timer->timer); + } } return PJ_SUCCESS; diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 84012080..fd8163a0 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -855,10 +855,25 @@ static void on_ice_complete(pjmedia_transport *tp, pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name, &pjsua_var.calls[id].med_rtp_addr)) { + pj_bool_t use_update; + const pj_str_t STR_UPDATE = { "UPDATE", 6 }; + pjsip_dialog_cap_status support_update; + pjsip_dialog *dlg; + + dlg = pjsua_var.calls[id].inv->dlg; + support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW, + NULL, &STR_UPDATE); + use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED); + PJ_LOG(4,(THIS_FILE, "ICE default transport address has changed for " - "call %d, sending UPDATE", id)); - pjsua_call_update(id, 0, NULL); + "call %d, sending %s", id, + (use_update ? "UPDATE" : "re-INVITE"))); + + if (use_update) + pjsua_call_update(id, 0, NULL); + else + pjsua_call_reinvite(id, 0, NULL); } } break; diff --git a/tests/pjsua/scripts-sipp/uas-timer-reinvite.xml b/tests/pjsua/scripts-sipp/uas-timer-reinvite.xml new file mode 100644 index 00000000..fe5169bb --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-timer-reinvite.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<!-- --> +<!-- Session timer where UAS doesn't indicate support for UPDATE. --> +<!-- In this case, UAC MUST use re-INVITE with SDP. --> + +<scenario name="Basic UAS responder"> + <recv request="INVITE" crlf="true"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Require: timer + Session-Expires: 90;refresher=uac + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=Some-UserAgent 68 210 IN IP4 [local_ip] + s=SIP Call + c=IN IP4 [local_ip] + t=0 0 + m=audio 17294 RTP/AVP 0 101 + c=IN IP4 [local_ip] + a=rtpmap:101 telephone-event/8000 + a=fmtp:101 0-16 + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="INVITE" crlf="true"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Require: timer + Session-Expires: 90;refresher=uac + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=Some-UserAgent 68 210 IN IP4 [local_ip] + s=SIP Call + c=IN IP4 [local_ip] + t=0 0 + m=audio 17294 RTP/AVP 0 101 + c=IN IP4 [local_ip] + a=rtpmap:101 telephone-event/8000 + a=fmtp:101 0-16 + ]]> + </send> + + <recv request="ACK" + rtd="true" + crlf="true"> + </recv> + + + <!-- Keep the call open for a while in case the 200 is lost to be --> + <!-- able to retransmit it if we receive the BYE again. --> + <pause milliseconds="4000"/> + + <!-- definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> + diff --git a/tests/pjsua/scripts-sipp/uas-timer-update.xml b/tests/pjsua/scripts-sipp/uas-timer-update.xml new file mode 100644 index 00000000..11a59734 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-timer-update.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<!-- --> +<!-- Session timer where UAS incidates support for UPDATE. --> +<!-- In this case, UAC will first use empty UPDATE, which we --> +<!-- will reply with 400. UAC MUST retry sending UPDATE with SDP. --> + +<scenario name="Basic UAS responder"> + <recv request="INVITE" crlf="true"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Allow: UPDATE, INVITE + Require: timer + Session-Expires: 90;refresher=uac + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=Some-UserAgent 68 210 IN IP4 [local_ip] + s=SIP Call + c=IN IP4 [local_ip] + t=0 0 + m=audio 17294 RTP/AVP 0 101 + c=IN IP4 [local_ip] + a=rtpmap:101 telephone-event/8000 + a=fmtp:101 0-16 + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="UPDATE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 400 Want SDP Body + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Allow: INVITE + Require: timer + Session-Expires: 90;refresher=uac + Content-Length: 0 + ]]> + </send> + + <recv request="UPDATE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Allow: INVITE + Require: timer + Session-Expires: 90;refresher=uac + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=Some-UserAgent 68 210 IN IP4 [local_ip] + s=SIP Call + c=IN IP4 [local_ip] + t=0 0 + m=audio 17294 RTP/AVP 0 101 + c=IN IP4 [local_ip] + a=rtpmap:101 telephone-event/8000 + a=fmtp:101 0-16 + ]]> + </send> + + + <!-- Keep the call open for a while in case the 200 is lost to be --> + <!-- able to retransmit it if we receive the BYE again. --> + <pause milliseconds="4000"/> + + <!-- definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> + |