diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2009-08-11 12:42:38 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2009-08-11 12:42:38 +0000 |
commit | 6f204c13ce8519524eb4da79359ac9b2aea08252 (patch) | |
tree | fd03248a6aa6c121822cbca2507113cf5b86b0f0 /pjsip/src/pjsip-ua/sip_timer.c | |
parent | 04fbadef1554da3b61c412e030081d1f05c6a99a (diff) |
Ticket #833:
- Initial version of Session Timers (RFC 4028).
- Added new options in pjsua app to configure Session Timers settings.
- Added python tests for Session Timers.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2858 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_timer.c')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_timer.c | 917 |
1 files changed, 917 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c new file mode 100644 index 00000000..628e0e2c --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_timer.c @@ -0,0 +1,917 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 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 <pj/log.h> +#include <pj/math.h> +#include <pj/os.h> +#include <pj/pool.h> + +#define THIS_FILE "sip_timer.c" + + +/* Constant values of Session Timers */ +#define ABS_MIN_SE 90 /* Absolute Min-SE, in seconds */ +#define DEF_SE 1800 /* Default SE, in seconds */ + + +/* 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 */ +typedef 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 */ + +} pjsip_timer; + +/* External global vars */ +extern pj_bool_t pjsip_use_compact_form; +extern const pjsip_method pjsip_update_method; + +/* 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; + +/* + * 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; + int printed; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + copy_advance(p, (*hname)); + *p++ = ':'; + *p++ = ' '; + + printed = pj_utoa(hdr->sess_expires, p); + p += printed; + + if (hdr->refresher.slen && (endbuf-p) > (hdr->refresher.slen + 2)) + { + *p++ = ';'; + copy_advance(p, STR_REFRESHER); + *p++ = '='; + copy_advance(p, hdr->refresher); + } + + printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; + return 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; + int printed; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + copy_advance(p, hdr->name); + *p++ = ':'; + *p++ = ' '; + + printed = pj_utoa(hdr->min_se, p); + p += printed; + + printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; + return 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. + */ +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; + + 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->role == PJSIP_ROLE_UAC) || + (inv->timer->refresher == TR_UAS && inv->role == PJSIP_ROLE_UAS); + + /* Do action based on role, refresher or refreshee */ + if (as_refresher) { + + pj_time_val now; + + /* Refresher, refresh the session */ + if (inv->timer->use_update) { + /* Create UPDATE request without offer */ + status = pjsip_inv_update(inv, NULL, NULL, &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, + 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, (inv->pool->obj_name, + "Refresh session after %ds (expiration period=%ds)", + (now.sec-inv->timer->last_refresh.sec), + inv->timer->setting.sess_expires)); + } else { + + pj_time_val now; + + /* Refreshee, terminate the session */ + status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT, + NULL, &tdata); + + pj_gettimeofday(&now); + PJ_LOG(3, (inv->pool->obj_name, + "No session refresh received after %ds " + "(expiration period=%ds), stopping session now!", + (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) { + status = pjsip_inv_send_msg(inv, tdata); + } + + /* 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)); + } +} + +/* Start Session Timers */ +static void start_timer(pjsip_inv_session *inv) +{ + pjsip_timer *timer = inv->timer; + pj_time_val delay = {0}; + + pj_assert(inv->timer->active == PJ_TRUE); + + stop_timer(inv); + + 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->role == PJSIP_ROLE_UAC) || + (timer->refresher == TR_UAS && inv->role == PJSIP_ROLE_UAS)) + { + /* 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; + } +} + +/* + * 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; + + is_initialized = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* + * Initialize Session Timers setting with default values. + */ +PJ_DEF(pj_status_t) pjsip_timer_default_setting(pjsip_timer_setting *setting) +{ + pj_bzero(setting, sizeof(pjsip_timer_setting)); + + setting->sess_expires = 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_default_setting(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); + + /* 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) +{ + const pjsip_msg *msg; + + PJ_ASSERT_RETURN(inv && rdata, 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_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 should contain Min-SE header */ + return PJ_SUCCESS; + } + + /* 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 request. */ + tdata = inv->invite_req; + + /* 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 */ + pjsip_inv_uac_restart(inv, PJ_FALSE); + 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) { + 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) + { + 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; + + PJ_TODO(CHECK_IF_REMOTE_SUPPORT_UPDATE); + + /* Finally, set active flag and start the Session Timers */ + inv->timer->active = PJ_TRUE; + start_timer(inv); + } + + 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_min_se_hdr *min_se_hdr; + pjsip_sess_expires_hdr *se_hdr; + const pjsip_msg *msg; + unsigned min_se; + + PJ_ASSERT_RETURN(inv && rdata, 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; + } + + /* 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. Note that Supported and + * Require headers negotiation should have been verified by invite + * session. + */ + 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; + } + } + + /* Make sure Session Timers is initialized */ + if (inv->timer == NULL) + pjsip_timer_init_session(inv, 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) + 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; + } + + /* 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 UAC support timer (currently check the existance of + * Session-Expires header in the request), set UAC as refresher. + */ + inv->timer->refresher = se_hdr? TR_UAC : TR_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) + { + /* Add Session-Expires header and start the timer */ + if (inv->timer && inv->timer->active) { + add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE); + 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; +} + |