1259 lines
36 KiB
C
1259 lines
36 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
|
|
*
|
|
* 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
|
|
*/
|
|
#include <pjsip-ua/sip_timer.h>
|
|
#include <pjsip/print_util.h>
|
|
#include <pjsip/sip_endpoint.h>
|
|
#include <pjsip/sip_event.h>
|
|
#include <pjsip/sip_transaction.h>
|
|
#include <pj/log.h>
|
|
#include <pj/math.h>
|
|
#include <pj/os.h>
|
|
#include <pj/pool.h>
|
|
|
|
#define THIS_FILE "sip_timer.c"
|
|
|
|
|
|
/* Constant of Session Timers */
|
|
#define ABS_MIN_SE 90 /* Absolute Min-SE, in seconds */
|
|
#define REFRESHER_EXPIRE_TIMER_ID 2 /* Refresher expire timer id */
|
|
|
|
/* String definitions */
|
|
static const pj_str_t STR_SE = {"Session-Expires", 15};
|
|
static const pj_str_t STR_SHORT_SE = {"x", 1};
|
|
static const pj_str_t STR_MIN_SE = {"Min-SE", 6};
|
|
static const pj_str_t STR_REFRESHER = {"refresher", 9};
|
|
static const pj_str_t STR_UAC = {"uac", 3};
|
|
static const pj_str_t STR_UAS = {"uas", 3};
|
|
static const pj_str_t STR_TIMER = {"timer", 5};
|
|
|
|
|
|
/* Enumeration of refresher */
|
|
enum timer_refresher {
|
|
TR_UNKNOWN,
|
|
TR_UAC,
|
|
TR_UAS
|
|
};
|
|
|
|
/* Structure definition of Session Timers */
|
|
struct pjsip_timer
|
|
{
|
|
pj_bool_t active; /**< Active/inactive flag */
|
|
pjsip_timer_setting setting; /**< Session Timers setting */
|
|
enum timer_refresher refresher; /**< Session refresher */
|
|
pj_time_val last_refresh; /**< Timestamp of last
|
|
refresh */
|
|
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. */
|
|
void *refresh_tdata; /**< The tdata of refresh
|
|
request */
|
|
pj_timer_entry expire_timer; /**< Timer entry for expire
|
|
refresher */
|
|
pj_int32_t last_422_cseq; /**< Last 422 resp CSeq. */
|
|
};
|
|
|
|
/* Local functions & vars */
|
|
static void stop_timer(pjsip_inv_session *inv);
|
|
static void start_timer(pjsip_inv_session *inv);
|
|
static pj_bool_t is_initialized;
|
|
const pjsip_method pjsip_update_method = { PJSIP_OTHER_METHOD, {"UPDATE", 6}};
|
|
/*
|
|
* Session-Expires header vptr.
|
|
*/
|
|
static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
|
|
char *buf, pj_size_t size);
|
|
static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
|
|
const pjsip_sess_expires_hdr *hdr);
|
|
static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
|
|
pj_pool_t *pool,
|
|
const pjsip_sess_expires_hdr* hdr);
|
|
|
|
static pjsip_hdr_vptr se_hdr_vptr =
|
|
{
|
|
(pjsip_hdr_clone_fptr) &se_hdr_clone,
|
|
(pjsip_hdr_clone_fptr) &se_hdr_shallow_clone,
|
|
(pjsip_hdr_print_fptr) &se_hdr_print,
|
|
};
|
|
|
|
/*
|
|
* Min-SE header vptr.
|
|
*/
|
|
static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
|
|
char *buf, pj_size_t size);
|
|
static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
|
|
const pjsip_min_se_hdr *hdr);
|
|
static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
|
|
pj_pool_t *pool,
|
|
const pjsip_min_se_hdr* hdr);
|
|
|
|
static pjsip_hdr_vptr min_se_hdr_vptr =
|
|
{
|
|
(pjsip_hdr_clone_fptr) &min_se_hdr_clone,
|
|
(pjsip_hdr_clone_fptr) &min_se_hdr_shallow_clone,
|
|
(pjsip_hdr_print_fptr) &min_se_hdr_print,
|
|
};
|
|
|
|
/*
|
|
* Session-Expires header vptr.
|
|
*/
|
|
static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
|
|
char *buf, pj_size_t size)
|
|
{
|
|
char *p = buf;
|
|
char *endbuf = buf+size;
|
|
pj_ssize_t printed;
|
|
const pjsip_parser_const_t *pc = pjsip_parser_const();
|
|
const pj_str_t *hname = pjsip_cfg()->endpt.use_compact_form?
|
|
&hdr->sname : &hdr->name;
|
|
|
|
/* Print header name and value */
|
|
if ((endbuf - p) < (hname->slen + 16))
|
|
return -1;
|
|
|
|
copy_advance(p, (*hname));
|
|
*p++ = ':';
|
|
*p++ = ' ';
|
|
|
|
printed = pj_utoa(hdr->sess_expires, p);
|
|
p += printed;
|
|
|
|
/* Print 'refresher' param */
|
|
if (hdr->refresher.slen)
|
|
{
|
|
if ((endbuf - p) < (STR_REFRESHER.slen + 2 + hdr->refresher.slen))
|
|
return -1;
|
|
|
|
*p++ = ';';
|
|
copy_advance(p, STR_REFRESHER);
|
|
*p++ = '=';
|
|
copy_advance(p, hdr->refresher);
|
|
}
|
|
|
|
/* Print generic params */
|
|
printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
|
|
&pc->pjsip_TOKEN_SPEC,
|
|
&pc->pjsip_TOKEN_SPEC, ';');
|
|
if (printed < 0)
|
|
return (int)printed;
|
|
|
|
p += printed;
|
|
return (int)(p - buf);
|
|
}
|
|
|
|
static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
|
|
const pjsip_sess_expires_hdr *hsrc)
|
|
{
|
|
pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(pool);
|
|
hdr->sess_expires = hsrc->sess_expires;
|
|
pj_strdup(pool, &hdr->refresher, &hsrc->refresher);
|
|
pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
|
|
pj_pool_t *pool,
|
|
const pjsip_sess_expires_hdr* hsrc)
|
|
{
|
|
pjsip_sess_expires_hdr *hdr = PJ_POOL_ALLOC_T(pool,pjsip_sess_expires_hdr);
|
|
pj_memcpy(hdr, hsrc, sizeof(*hdr));
|
|
pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
/*
|
|
* Min-SE header vptr.
|
|
*/
|
|
static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
|
|
char *buf, pj_size_t size)
|
|
{
|
|
char *p = buf;
|
|
char *endbuf = buf+size;
|
|
pj_ssize_t printed;
|
|
const pjsip_parser_const_t *pc = pjsip_parser_const();
|
|
|
|
/* Print header name and value */
|
|
if ((endbuf - p) < (hdr->name.slen + 16))
|
|
return -1;
|
|
|
|
copy_advance(p, hdr->name);
|
|
*p++ = ':';
|
|
*p++ = ' ';
|
|
|
|
printed = pj_utoa(hdr->min_se, p);
|
|
p += printed;
|
|
|
|
/* Print generic params */
|
|
printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
|
|
&pc->pjsip_TOKEN_SPEC,
|
|
&pc->pjsip_TOKEN_SPEC, ';');
|
|
if (printed < 0)
|
|
return (int)printed;
|
|
|
|
p += printed;
|
|
return (int)(p - buf);
|
|
}
|
|
|
|
static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
|
|
const pjsip_min_se_hdr *hsrc)
|
|
{
|
|
pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(pool);
|
|
hdr->min_se = hsrc->min_se;
|
|
pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
|
|
pj_pool_t *pool,
|
|
const pjsip_min_se_hdr* hsrc)
|
|
{
|
|
pjsip_min_se_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_min_se_hdr);
|
|
pj_memcpy(hdr, hsrc, sizeof(*hdr));
|
|
pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse Session-Expires header.
|
|
*/
|
|
static pjsip_hdr *parse_hdr_se(pjsip_parse_ctx *ctx)
|
|
{
|
|
pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(ctx->pool);
|
|
const pjsip_parser_const_t *pc = pjsip_parser_const();
|
|
pj_str_t token;
|
|
|
|
pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
|
|
hdr->sess_expires = pj_strtoul(&token);
|
|
|
|
while (*ctx->scanner->curptr == ';') {
|
|
pj_str_t pname, pvalue;
|
|
|
|
pj_scan_get_char(ctx->scanner);
|
|
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
|
|
|
|
if (pj_stricmp(&pname, &STR_REFRESHER)==0) {
|
|
hdr->refresher = pvalue;
|
|
} else {
|
|
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
|
|
param->name = pname;
|
|
param->value = pvalue;
|
|
pj_list_push_back(&hdr->other_param, param);
|
|
}
|
|
}
|
|
pjsip_parse_end_hdr_imp( ctx->scanner );
|
|
return (pjsip_hdr*)hdr;
|
|
}
|
|
|
|
/*
|
|
* Parse Min-SE header.
|
|
*/
|
|
static pjsip_hdr *parse_hdr_min_se(pjsip_parse_ctx *ctx)
|
|
{
|
|
pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(ctx->pool);
|
|
const pjsip_parser_const_t *pc = pjsip_parser_const();
|
|
pj_str_t token;
|
|
|
|
pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
|
|
hdr->min_se = pj_strtoul(&token);
|
|
|
|
while (*ctx->scanner->curptr == ';') {
|
|
pj_str_t pname, pvalue;
|
|
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
|
|
|
|
pj_scan_get_char(ctx->scanner);
|
|
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
|
|
|
|
param->name = pname;
|
|
param->value = pvalue;
|
|
pj_list_push_back(&hdr->other_param, param);
|
|
}
|
|
pjsip_parse_end_hdr_imp( ctx->scanner );
|
|
return (pjsip_hdr*)hdr;
|
|
}
|
|
|
|
|
|
/* Add "Session-Expires" and "Min-SE" headers. Note that "Min-SE" header
|
|
* can only be added to INVITE/UPDATE request and 422 response.
|
|
*/
|
|
static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata,
|
|
pj_bool_t add_se, pj_bool_t add_min_se)
|
|
{
|
|
pjsip_timer *timer = inv->timer;
|
|
|
|
/* Add Session-Expires header */
|
|
if (add_se) {
|
|
pjsip_sess_expires_hdr *hdr;
|
|
|
|
hdr = pjsip_sess_expires_hdr_create(tdata->pool);
|
|
hdr->sess_expires = timer->setting.sess_expires;
|
|
if (timer->refresher != TR_UNKNOWN)
|
|
hdr->refresher = (timer->refresher == TR_UAC? STR_UAC : STR_UAS);
|
|
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
|
|
}
|
|
|
|
/* Add Min-SE header */
|
|
if (add_min_se) {
|
|
pjsip_min_se_hdr *hdr;
|
|
|
|
hdr = pjsip_min_se_hdr_create(tdata->pool);
|
|
hdr->min_se = timer->setting.min_se;
|
|
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
|
|
}
|
|
}
|
|
|
|
/* Timer callback. When the timer is fired, it can be time to refresh
|
|
* the session if UA is the refresher, otherwise it is time to end
|
|
* the session.
|
|
*/
|
|
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;
|
|
pj_status_t status;
|
|
pj_bool_t as_refresher;
|
|
int entry_id;
|
|
char obj_name[PJ_MAX_OBJ_NAME];
|
|
|
|
pj_assert(inv);
|
|
|
|
PJ_UNUSED_ARG(timer_heap);
|
|
|
|
/* Lock dialog. */
|
|
pjsip_dlg_inc_lock(inv->dlg);
|
|
|
|
/* Check our role */
|
|
as_refresher =
|
|
(inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
|
|
(inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS);
|
|
|
|
entry_id = entry->id;
|
|
pj_ansi_strncpy(obj_name, inv->pool->obj_name, PJ_MAX_OBJ_NAME);
|
|
|
|
/* Do action based on role(refresher or refreshee).
|
|
* As refresher:
|
|
* - send refresh, or
|
|
* - end session if there is no response to the refresh request.
|
|
* As refreshee:
|
|
* - end session if there is no refresh request received.
|
|
*/
|
|
if (as_refresher && (entry_id != REFRESHER_EXPIRE_TIMER_ID)) {
|
|
pj_time_val now;
|
|
|
|
/* As refresher, reshedule the refresh request on the following:
|
|
* - must 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);
|
|
|
|
inv->timer->timer.id = 0;
|
|
|
|
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);
|
|
|
|
PJ_LOG(3, (obj_name,
|
|
"Reschedule refresh request after %d seconds as "
|
|
"there is another SDP negotiation in progress",
|
|
delay.sec));
|
|
return;
|
|
}
|
|
|
|
/* Refresher, refresh the session */
|
|
if (inv->timer->use_update) {
|
|
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;
|
|
const pjmedia_sdp_session *offer = NULL;
|
|
|
|
pj_assert(pjmedia_sdp_neg_get_state(inv->neg) ==
|
|
PJMEDIA_SDP_NEG_STATE_DONE);
|
|
|
|
status = pjsip_inv_invite(inv, &tdata);
|
|
if (status == PJ_SUCCESS)
|
|
status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov,
|
|
inv->neg, &offer);
|
|
if (status == PJ_SUCCESS)
|
|
status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
|
|
if (status == PJ_SUCCESS) {
|
|
status = pjsip_create_sdp_body(tdata->pool,
|
|
(pjmedia_sdp_session*)offer, &body);
|
|
tdata->msg->body = body;
|
|
}
|
|
}
|
|
|
|
pj_gettimeofday(&now);
|
|
PJ_LOG(4, (obj_name,
|
|
"Refreshing session after %ds (expiration period=%ds)",
|
|
(now.sec-inv->timer->last_refresh.sec),
|
|
inv->timer->setting.sess_expires));
|
|
} else {
|
|
|
|
pj_time_val now;
|
|
|
|
if (as_refresher)
|
|
inv->timer->expire_timer.id = 0;
|
|
else
|
|
inv->timer->timer.id = 0;
|
|
|
|
/* Terminate the session */
|
|
status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT,
|
|
NULL, &tdata);
|
|
|
|
pj_gettimeofday(&now);
|
|
PJ_LOG(3, (obj_name,
|
|
"No session %s received after %ds "
|
|
"(expiration period=%ds), stopping session now!",
|
|
(as_refresher?"refresh response":"refresh"),
|
|
(now.sec-inv->timer->last_refresh.sec),
|
|
inv->timer->setting.sess_expires));
|
|
|
|
}
|
|
|
|
/* Unlock dialog. */
|
|
pjsip_dlg_dec_lock(inv->dlg);
|
|
|
|
/* Send message, if any */
|
|
if (tdata && status == PJ_SUCCESS) {
|
|
inv->timer->refresh_tdata = tdata;
|
|
|
|
status = pjsip_inv_send_msg(inv, tdata);
|
|
}
|
|
|
|
/*
|
|
* At this point, dialog might have already been destroyed,
|
|
* including its pool used by the invite session.
|
|
*/
|
|
|
|
/* Print error message, if any */
|
|
if (status != PJ_SUCCESS) {
|
|
PJ_PERROR(2, (obj_name, status,
|
|
"Error in %s session timer",
|
|
((as_refresher && entry_id != REFRESHER_EXPIRE_TIMER_ID)?
|
|
"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};
|
|
|
|
pj_assert(inv->timer->active == PJ_TRUE);
|
|
|
|
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 */
|
|
timer_cb); /* callback */
|
|
|
|
/* Set delay based on role, refresher or refreshee */
|
|
if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
|
|
(timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
|
|
{
|
|
/* Add refresher expire timer */
|
|
pj_timer_entry_init(&timer->expire_timer,
|
|
REFRESHER_EXPIRE_TIMER_ID, /* id */
|
|
inv, /* user data */
|
|
timer_cb); /* callback */
|
|
|
|
delay.sec = timer->setting.sess_expires;
|
|
/* Schedule the timer */
|
|
pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->expire_timer,
|
|
&delay);
|
|
|
|
/* Next refresh, the delay is half of session expire */
|
|
delay.sec = timer->setting.sess_expires / 2;
|
|
} else {
|
|
/* Send BYE if no refresh received until this timer fired, delay
|
|
* is the minimum of 32 seconds and one third of the session interval
|
|
* before session expiration.
|
|
*/
|
|
delay.sec = timer->setting.sess_expires -
|
|
timer->setting.sess_expires/3;
|
|
delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
|
|
}
|
|
|
|
/* Schedule the timer */
|
|
pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);
|
|
|
|
/* Update last refresh time */
|
|
pj_gettimeofday(&timer->last_refresh);
|
|
}
|
|
|
|
/* Stop Session Timers */
|
|
static void stop_timer(pjsip_inv_session *inv)
|
|
{
|
|
if (inv->timer->timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->timer);
|
|
inv->timer->timer.id = 0;
|
|
}
|
|
|
|
if (inv->timer->expire_timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->expire_timer);
|
|
inv->timer->expire_timer.id = 0;
|
|
}
|
|
}
|
|
|
|
/* Deinitialize Session Timers */
|
|
static void pjsip_timer_deinit_module(pjsip_endpoint *endpt)
|
|
{
|
|
PJ_TODO(provide_initialized_flag_for_each_endpoint);
|
|
PJ_UNUSED_ARG(endpt);
|
|
is_initialized = PJ_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Initialize Session Timers support in PJSIP.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt)
|
|
{
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
|
|
|
|
if (is_initialized)
|
|
return PJ_SUCCESS;
|
|
|
|
/* Register Session-Expires header parser */
|
|
status = pjsip_register_hdr_parser( STR_SE.ptr, STR_SHORT_SE.ptr,
|
|
&parse_hdr_se);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Register Min-SE header parser */
|
|
status = pjsip_register_hdr_parser( STR_MIN_SE.ptr, NULL,
|
|
&parse_hdr_min_se);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Register 'timer' capability to endpoint */
|
|
status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED,
|
|
NULL, 1, &STR_TIMER);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Register deinit module to be executed when PJLIB shutdown */
|
|
if (pjsip_endpt_atexit(endpt, &pjsip_timer_deinit_module) != PJ_SUCCESS) {
|
|
/* Failure to register this function may cause this module won't
|
|
* work properly when the stack is restarted (without quitting
|
|
* application).
|
|
*/
|
|
pj_assert(!"Failed to register Session Timer deinit.");
|
|
PJ_LOG(1, (THIS_FILE, "Failed to register Session Timer deinit."));
|
|
}
|
|
|
|
is_initialized = PJ_TRUE;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize Session Timers setting with default values.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_setting_default(pjsip_timer_setting *setting)
|
|
{
|
|
pj_bzero(setting, sizeof(pjsip_timer_setting));
|
|
|
|
setting->sess_expires = PJSIP_SESS_TIMER_DEF_SE;
|
|
setting->min_se = ABS_MIN_SE;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Initialize Session Timers in an INVITE session.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_init_session(
|
|
pjsip_inv_session *inv,
|
|
const pjsip_timer_setting *setting)
|
|
{
|
|
pjsip_timer_setting *s;
|
|
|
|
pj_assert(is_initialized);
|
|
PJ_ASSERT_RETURN(inv, PJ_EINVAL);
|
|
|
|
/* Allocate and/or reset Session Timers structure */
|
|
if (!inv->timer)
|
|
inv->timer = PJ_POOL_ZALLOC_T(inv->pool, pjsip_timer);
|
|
else
|
|
pj_bzero(inv->timer, sizeof(pjsip_timer));
|
|
|
|
s = &inv->timer->setting;
|
|
|
|
/* Init Session Timers setting */
|
|
if (setting) {
|
|
PJ_ASSERT_RETURN(setting->min_se >= ABS_MIN_SE,
|
|
PJ_ETOOSMALL);
|
|
PJ_ASSERT_RETURN(setting->sess_expires >= setting->min_se,
|
|
PJ_EINVAL);
|
|
|
|
pj_memcpy(s, setting, sizeof(*s));
|
|
} else {
|
|
pjsip_timer_setting_default(s);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create Session-Expires header.
|
|
*/
|
|
PJ_DEF(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create(
|
|
pj_pool_t *pool)
|
|
{
|
|
pjsip_sess_expires_hdr *hdr = PJ_POOL_ZALLOC_T(pool,
|
|
pjsip_sess_expires_hdr);
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
hdr->type = PJSIP_H_OTHER;
|
|
hdr->name = STR_SE;
|
|
hdr->sname = STR_SHORT_SE;
|
|
hdr->vptr = &se_hdr_vptr;
|
|
pj_list_init(hdr);
|
|
pj_list_init(&hdr->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create Min-SE header.
|
|
*/
|
|
PJ_DEF(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool)
|
|
{
|
|
pjsip_min_se_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_min_se_hdr);
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
hdr->type = PJSIP_H_OTHER;
|
|
hdr->name = STR_MIN_SE;
|
|
hdr->vptr = &min_se_hdr_vptr;
|
|
pj_list_init(hdr);
|
|
pj_list_init(&hdr->other_param);
|
|
return hdr;
|
|
}
|
|
|
|
|
|
/*
|
|
* This function generates headers for Session Timers for intial and
|
|
* refresh INVITE or UPDATE.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv,
|
|
pjsip_tx_data *tdata)
|
|
{
|
|
PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
|
|
|
|
/* Check if Session Timers is supported */
|
|
if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
/* Make sure Session Timers is initialized */
|
|
if (inv->timer == NULL)
|
|
pjsip_timer_init_session(inv, NULL);
|
|
|
|
/* If refresher role (i.e: ours or peer) has been set/negotiated,
|
|
* better to keep it.
|
|
*/
|
|
if (inv->timer->refresher != TR_UNKNOWN) {
|
|
pj_bool_t as_refresher;
|
|
|
|
/* Check our refresher role */
|
|
as_refresher =
|
|
(inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
|
|
(inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
|
|
|
|
/* Update transaction role */
|
|
inv->timer->role = PJSIP_ROLE_UAC;
|
|
|
|
/* Update refresher role */
|
|
inv->timer->refresher = as_refresher? TR_UAC : TR_UAS;
|
|
}
|
|
|
|
/* Add Session Timers headers */
|
|
add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* This function will handle Session Timers part of INVITE/UPDATE
|
|
* responses with code:
|
|
* - 422 (Session Interval Too Small)
|
|
* - 2xx final response
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv,
|
|
const pjsip_rx_data *rdata,
|
|
pjsip_status_code *st_code)
|
|
{
|
|
const pjsip_msg *msg;
|
|
|
|
PJ_ASSERT_ON_FAIL(inv && rdata,
|
|
{if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
|
|
|
|
/* Check if Session Timers is supported */
|
|
if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0) {
|
|
if (rdata->msg_info.msg->line.status.code ==
|
|
PJSIP_SC_SESSION_TIMER_TOO_SMALL)
|
|
{
|
|
return PJSIP_EINVALIDSTATUS;
|
|
} else {
|
|
return PJ_SUCCESS;
|
|
}
|
|
}
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
msg = rdata->msg_info.msg;
|
|
pj_assert(msg->type == PJSIP_RESPONSE_MSG);
|
|
|
|
/* Only process response of INVITE or UPDATE */
|
|
if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD &&
|
|
pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
|
|
{
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) {
|
|
/* Our Session-Expires is too small, let's update it based on
|
|
* Min-SE header in the response.
|
|
*/
|
|
pjsip_tx_data *tdata;
|
|
pjsip_min_se_hdr *min_se_hdr;
|
|
pjsip_hdr *hdr;
|
|
pjsip_via_hdr *via;
|
|
|
|
/* Get Min-SE value from response */
|
|
min_se_hdr = (pjsip_min_se_hdr*)
|
|
pjsip_msg_find_hdr_by_name(msg, &STR_MIN_SE, NULL);
|
|
if (min_se_hdr == NULL) {
|
|
/* Response 422 MUST contain Min-SE header */
|
|
PJ_LOG(3, (inv->pool->obj_name,
|
|
"Received 422 (Session Interval Too Small) response "
|
|
"without Min-SE header!"));
|
|
|
|
pjsip_timer_end_session(inv);
|
|
return PJSIP_EMISSINGHDR;
|
|
}
|
|
|
|
/* Avoid 422 retry loop. Ignore it and stop retrying if dialog is
|
|
* established, otherwise, just return error to disconnect the call.
|
|
*/
|
|
if (rdata->msg_info.cseq->cseq == inv->timer->last_422_cseq + 1) {
|
|
if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
|
|
inv->invite_tsx = NULL;
|
|
return PJ_SUCCESS;
|
|
} else {
|
|
return PJSIP_ERRNO_FROM_SIP_STATUS(
|
|
PJSIP_SC_SESSION_TIMER_TOO_SMALL);
|
|
}
|
|
}
|
|
inv->timer->last_422_cseq = rdata->msg_info.cseq->cseq;
|
|
|
|
/* Session Timers should have been initialized here */
|
|
pj_assert(inv->timer);
|
|
|
|
/* Update Min-SE */
|
|
inv->timer->setting.min_se = PJ_MAX(min_se_hdr->min_se,
|
|
inv->timer->setting.min_se);
|
|
|
|
/* Update Session Timers setting */
|
|
if (inv->timer->setting.sess_expires < inv->timer->setting.min_se)
|
|
inv->timer->setting.sess_expires = inv->timer->setting.min_se;
|
|
|
|
/* Prepare to restart the request */
|
|
|
|
/* Get the original INVITE/UPDATE request. */
|
|
tdata = pjsip_rdata_get_tsx((pjsip_rx_data*)rdata)->last_tx;
|
|
|
|
/* Remove branch param in Via header. */
|
|
via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
|
|
pj_assert(via);
|
|
via->branch_param.slen = 0;
|
|
|
|
/* Restore strict route set.
|
|
* See http://trac.pjsip.org/repos/ticket/492
|
|
*/
|
|
pjsip_restore_strict_route_set(tdata);
|
|
|
|
/* Must invalidate the message! */
|
|
pjsip_tx_data_invalidate_msg(tdata);
|
|
|
|
pjsip_tx_data_add_ref(tdata);
|
|
|
|
/* Update Session Timers headers */
|
|
hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg,
|
|
&STR_MIN_SE, NULL);
|
|
if (hdr != NULL) pj_list_erase(hdr);
|
|
|
|
hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_names(tdata->msg, &STR_SE,
|
|
&STR_SHORT_SE, NULL);
|
|
if (hdr != NULL) pj_list_erase(hdr);
|
|
|
|
add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
|
|
|
|
/* Restart UAC if this is initial INVITE, or simply reset tsx for
|
|
* subsequent INVITE.
|
|
*/
|
|
if (inv->state < PJSIP_INV_STATE_CONFIRMED)
|
|
pjsip_inv_uac_restart(inv, PJ_FALSE);
|
|
else if (tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD)
|
|
inv->invite_tsx = NULL;
|
|
|
|
/* Resend the updated request */
|
|
pjsip_inv_send_msg(inv, tdata);
|
|
|
|
return PJ_SUCCESS;
|
|
|
|
} else if (msg->line.status.code/100 == 2) {
|
|
|
|
pjsip_sess_expires_hdr *se_hdr;
|
|
|
|
/* Find Session-Expires header */
|
|
se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
|
|
msg, &STR_SE,
|
|
&STR_SHORT_SE, NULL);
|
|
if (se_hdr == NULL) {
|
|
/* Remote doesn't support/want Session Timers, check if local
|
|
* require or force to use Session Timers.
|
|
*/
|
|
if (inv->options & PJSIP_INV_REQUIRE_TIMER) {
|
|
if (st_code)
|
|
*st_code = PJSIP_SC_EXTENSION_REQUIRED;
|
|
pjsip_timer_end_session(inv);
|
|
return PJSIP_ERRNO_FROM_SIP_STATUS(
|
|
PJSIP_SC_EXTENSION_REQUIRED);
|
|
}
|
|
|
|
if ((inv->options & PJSIP_INV_ALWAYS_USE_TIMER) == 0) {
|
|
/* Session Timers not forced */
|
|
pjsip_timer_end_session(inv);
|
|
return PJ_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* Make sure Session Timers is initialized */
|
|
if (inv->timer == NULL)
|
|
pjsip_timer_init_session(inv, NULL);
|
|
|
|
/* Session expiration period specified by remote is lower than our
|
|
* Min-SE.
|
|
*/
|
|
if (se_hdr &&
|
|
se_hdr->sess_expires < inv->timer->setting.min_se)
|
|
{
|
|
/* See ticket #954, instead of returning non-PJ_SUCCESS (which
|
|
* may cause disconnecting call/dialog), let's just accept the
|
|
* SE and update our local SE, as long as it isn't less than 90s.
|
|
*/
|
|
if (se_hdr->sess_expires >= ABS_MIN_SE) {
|
|
PJ_LOG(3, (inv->pool->obj_name,
|
|
"Peer responds with bad Session-Expires, %ds, "
|
|
"which is less than Min-SE specified in request, "
|
|
"%ds. Well, let's just accept and use it.",
|
|
se_hdr->sess_expires, inv->timer->setting.min_se));
|
|
|
|
inv->timer->setting.sess_expires = se_hdr->sess_expires;
|
|
inv->timer->setting.min_se = se_hdr->sess_expires;
|
|
}
|
|
|
|
//if (st_code)
|
|
// *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
|
|
//pjsip_timer_end_session(inv);
|
|
//return PJSIP_ERRNO_FROM_SIP_STATUS(
|
|
// PJSIP_SC_SESSION_TIMER_TOO_SMALL);
|
|
}
|
|
|
|
/* Update SE. Session-Expires in response cannot be lower than Min-SE.
|
|
* Session-Expires in response can only be equal or lower than in
|
|
* request.
|
|
*/
|
|
if (se_hdr &&
|
|
se_hdr->sess_expires <= inv->timer->setting.sess_expires &&
|
|
se_hdr->sess_expires >= inv->timer->setting.min_se)
|
|
{
|
|
/* Good SE from remote, update local SE */
|
|
inv->timer->setting.sess_expires = se_hdr->sess_expires;
|
|
}
|
|
|
|
/* Set the refresher */
|
|
if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
|
|
inv->timer->refresher = TR_UAC;
|
|
else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
|
|
inv->timer->refresher = TR_UAS;
|
|
else
|
|
/* UAS should set the refresher, however, there is a case that
|
|
* UAS doesn't support/want Session Timers but the UAC insists
|
|
* to use Session Timers.
|
|
*/
|
|
inv->timer->refresher = TR_UAC;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_timer_handle_refresh_error(
|
|
pjsip_inv_session *inv,
|
|
pjsip_event *event)
|
|
{
|
|
PJ_ASSERT_RETURN(inv && event, PJ_EINVAL);
|
|
|
|
/* Check if Session Timers is supported */
|
|
if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
if (inv->timer && inv->timer->active) {
|
|
pj_bool_t as_refresher;
|
|
|
|
/* Check our role */
|
|
as_refresher = ((inv->timer->refresher == TR_UAC) &&
|
|
(inv->timer->role == PJSIP_ROLE_UAC)) ||
|
|
((inv->timer->refresher == TR_UAS) &&
|
|
(inv->timer->role == PJSIP_ROLE_UAS));
|
|
|
|
|
|
if ((as_refresher) && (event->type == PJSIP_EVENT_TSX_STATE) &&
|
|
(inv->timer->refresh_tdata == event->body.tsx_state.tsx->last_tx))
|
|
{
|
|
pjsip_status_code st_code;
|
|
pjsip_tx_data *bye;
|
|
pj_status_t status;
|
|
|
|
st_code =
|
|
(pjsip_status_code)event->body.tsx_state.tsx->status_code;
|
|
|
|
PJ_LOG(3, (inv->pool->obj_name,
|
|
"Receive error %d for refresh request %.*s/cseq=%d",
|
|
st_code, event->body.tsx_state.tsx->method.name.slen,
|
|
event->body.tsx_state.tsx->method.name.ptr,
|
|
event->body.tsx_state.tsx->cseq));
|
|
|
|
if (st_code == 503 && PJSIP_SESS_TIMER_RETRY_DELAY >= 0) {
|
|
/* Retry the refresh after some delay */
|
|
pj_time_val delay = {PJSIP_SESS_TIMER_RETRY_DELAY, 0};
|
|
|
|
PJ_LOG(3, (inv->pool->obj_name, "Scheduling to retry refresh "
|
|
"request after %d second(s)", delay.sec));
|
|
|
|
inv->timer->timer.id = 1;
|
|
pjsip_endpt_schedule_timer(inv->dlg->endpt,
|
|
&inv->timer->timer, &delay);
|
|
} else {
|
|
PJ_LOG(3, (inv->pool->obj_name, "Ending session now"));
|
|
|
|
status = pjsip_inv_end_session(inv,
|
|
event->body.tsx_state.tsx->status_code,
|
|
pjsip_get_status_text(st_code),
|
|
&bye);
|
|
|
|
if (status == PJ_SUCCESS && bye)
|
|
status = pjsip_inv_send_msg(inv, bye);
|
|
}
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Handle incoming INVITE or UPDATE request.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv,
|
|
const pjsip_rx_data *rdata,
|
|
pjsip_status_code *st_code)
|
|
{
|
|
pjsip_min_se_hdr *min_se_hdr;
|
|
pjsip_sess_expires_hdr *se_hdr;
|
|
const pjsip_msg *msg;
|
|
unsigned min_se;
|
|
|
|
PJ_ASSERT_ON_FAIL(inv && rdata,
|
|
{if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
|
|
|
|
/* Check if Session Timers is supported */
|
|
if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
pj_assert(is_initialized);
|
|
|
|
msg = rdata->msg_info.msg;
|
|
pj_assert(msg->type == PJSIP_REQUEST_MSG);
|
|
|
|
/* Only process INVITE or UPDATE request */
|
|
if (msg->line.req.method.id != PJSIP_INVITE_METHOD &&
|
|
pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
|
|
{
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Make sure Session Timers is initialized */
|
|
if (inv->timer == NULL)
|
|
pjsip_timer_init_session(inv, NULL);
|
|
|
|
/* Find Session-Expires header */
|
|
se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
|
|
msg, &STR_SE, &STR_SHORT_SE, NULL);
|
|
/* Find Min-SE header */
|
|
min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(msg,
|
|
&STR_MIN_SE, NULL);
|
|
/* Update Min-SE */
|
|
min_se = inv->timer->setting.min_se;
|
|
if (min_se_hdr)
|
|
min_se = PJ_MAX(min_se_hdr->min_se, min_se);
|
|
|
|
/* Validate SE. Session-Expires cannot be lower than Min-SE
|
|
* (or 90 seconds if Min-SE is not set).
|
|
*/
|
|
if (se_hdr && se_hdr->sess_expires < min_se) {
|
|
if (st_code)
|
|
*st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
|
|
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_SESSION_TIMER_TOO_SMALL);
|
|
}
|
|
|
|
/* Update SE. Note that there is a case that SE is not available in the
|
|
* request (which means remote doesn't want/support it), but local insists
|
|
* to use Session Timers.
|
|
*/
|
|
if (se_hdr) {
|
|
/* Update SE as specified by peer. */
|
|
inv->timer->setting.sess_expires = se_hdr->sess_expires;
|
|
} else if (inv->timer->setting.sess_expires < min_se) {
|
|
/* There is no SE in the request (remote support Session Timers but
|
|
* doesn't want to use it, it just specify Min-SE) and local SE is
|
|
* lower than Min-SE specified by remote.
|
|
*/
|
|
inv->timer->setting.sess_expires = min_se;
|
|
}
|
|
|
|
/* If incoming request has no SE header, it means remote doesn't
|
|
* support/want Session Timers. So let's check if local require or
|
|
* force to use Session Timers. Note that Supported and Require headers
|
|
* negotiation should have been verified by invite session.
|
|
*/
|
|
if (se_hdr == NULL) {
|
|
if ((inv->options &
|
|
(PJSIP_INV_REQUIRE_TIMER | PJSIP_INV_ALWAYS_USE_TIMER)) == 0)
|
|
{
|
|
/* Session Timers not forced/required */
|
|
pjsip_timer_end_session(inv);
|
|
return PJ_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* Set the refresher */
|
|
if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
|
|
inv->timer->refresher = TR_UAC;
|
|
else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
|
|
inv->timer->refresher = TR_UAS;
|
|
else {
|
|
/* If refresher role (i.e: ours or peer) has been set/negotiated,
|
|
* better to keep it.
|
|
*/
|
|
if (inv->timer->refresher != TR_UNKNOWN) {
|
|
pj_bool_t as_refresher;
|
|
|
|
/* Check our refresher role */
|
|
as_refresher =
|
|
(inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
|
|
(inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
|
|
|
|
/* Update refresher role */
|
|
inv->timer->refresher = as_refresher? TR_UAS : TR_UAC;
|
|
} else {
|
|
/* If UAC supports timer and Session-Expires header is present
|
|
* in the request, set UAC as refresher.
|
|
* If UAC doesn't support timer and a proxy inserts a
|
|
* Session-Expires header, then UAS has to be the
|
|
* refresher (according to RFC 4028 Section 9).
|
|
*/
|
|
pj_bool_t uac_supports_timer = PJ_FALSE;
|
|
pjsip_supported_hdr *sup_hdr;
|
|
|
|
sup_hdr = (pjsip_supported_hdr*)
|
|
pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);
|
|
if (sup_hdr) {
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sup_hdr->count; i++) {
|
|
if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER) == 0) {
|
|
uac_supports_timer = PJ_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
inv->timer->refresher = (uac_supports_timer && se_hdr)? TR_UAC:
|
|
TR_UAS;
|
|
}
|
|
}
|
|
|
|
/* Remember our role in this transaction */
|
|
inv->timer->role = PJSIP_ROLE_UAS;
|
|
|
|
/* Set active flag */
|
|
inv->timer->active = PJ_TRUE;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Handle outgoing response with status code 2xx & 422.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv,
|
|
pjsip_tx_data *tdata)
|
|
{
|
|
pjsip_msg *msg;
|
|
|
|
/* Check if Session Timers is supported */
|
|
if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
pj_assert(is_initialized);
|
|
PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
|
|
|
|
msg = tdata->msg;
|
|
|
|
if (msg->line.status.code/100 == 2)
|
|
{
|
|
if (inv->timer && inv->timer->active) {
|
|
/* Add Session-Expires header and start the timer */
|
|
add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE);
|
|
|
|
/* Add 'timer' to Require header (see ticket #1560). */
|
|
if (inv->timer->refresher == TR_UAC) {
|
|
pjsip_require_hdr *req_hdr;
|
|
pj_bool_t req_hdr_has_timer = PJ_FALSE;
|
|
|
|
req_hdr = (pjsip_require_hdr*)
|
|
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_REQUIRE,
|
|
NULL);
|
|
if (req_hdr == NULL) {
|
|
req_hdr = pjsip_require_hdr_create(tdata->pool);
|
|
PJ_ASSERT_RETURN(req_hdr, PJ_ENOMEM);
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
|
|
} else {
|
|
unsigned i;
|
|
for (i = 0; i < req_hdr->count; ++i) {
|
|
if (pj_stricmp(&req_hdr->values[i], &STR_TIMER)) {
|
|
req_hdr_has_timer = PJ_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!req_hdr_has_timer)
|
|
req_hdr->values[req_hdr->count++] = STR_TIMER;
|
|
}
|
|
|
|
/* Finally, start timer. */
|
|
start_timer(inv);
|
|
}
|
|
}
|
|
else if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
|
|
{
|
|
/* Add Min-SE header */
|
|
add_timer_headers(inv, tdata, PJ_FALSE, PJ_TRUE);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* End the Session Timers.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv)
|
|
{
|
|
PJ_ASSERT_RETURN(inv, PJ_EINVAL);
|
|
|
|
if (inv->timer) {
|
|
/* Reset active flag */
|
|
inv->timer->active = PJ_FALSE;
|
|
|
|
/* Stop Session Timers */
|
|
stop_timer(inv);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|