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: https://svn.pjsip.org/repos/pjproject/trunk@2858 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/build/pjsip_ua.vcproj b/pjsip/build/pjsip_ua.vcproj
index 904bae4..6a01144 100644
--- a/pjsip/build/pjsip_ua.vcproj
+++ b/pjsip/build/pjsip_ua.vcproj
Binary files differ
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
index 60a6238..e85b00b 100644
--- a/pjsip/include/pjsip-ua/sip_inv.h
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -305,8 +305,16 @@
      */
     PJSIP_INV_REQUIRE_TIMER	= 64,
 
+    /**  
+     * Session timer extension will always be used even when peer doesn't
+     * support/want session timer.
+     */
+    PJSIP_INV_ALWAYS_USE_TIMER	= 128,
+
 };
 
+/* Forward declaration of Session Timers */
+struct pjsip_timer;
 
 /**
  * This structure describes the invite session.
@@ -331,6 +339,7 @@
     pjsip_tx_data	*last_ack;		    /**< Last ACK request   */
     pj_int32_t		 last_ack_cseq;		    /**< CSeq of last ACK   */
     void		*mod_data[PJSIP_MAX_MODULE];/**< Modules data.	    */
+    struct pjsip_timer	*timer;			    /**< Session Timers.    */
 };
 
 
diff --git a/pjsip/include/pjsip-ua/sip_timer.h b/pjsip/include/pjsip-ua/sip_timer.h
new file mode 100644
index 0000000..cb77fae
--- /dev/null
+++ b/pjsip/include/pjsip-ua/sip_timer.h
@@ -0,0 +1,256 @@
+/* $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 
+ */
+#ifndef __PJSIP_TIMER_H__
+#define __PJSIP_TIMER_H__
+
+
+/**
+ * @file sip_timer.h
+ * @brief SIP Session Timers support (RFC 4028 - Session Timer in SIP)
+ */
+
+#include <pjsip-ua/sip_inv.h>
+#include <pjsip/sip_msg.h>
+
+/**
+ * @defgroup PJSIP_TIMER SIP Session Timers support (RFC 4028 - Session Timers in SIP)
+ * @ingroup PJSIP_HIGH_UA
+ * @brief SIP Session Timers support (RFC 4028 - Session Timers in SIP)
+ * @{
+ *
+ * \section PJSIP_TIMER_REFERENCE References
+ *
+ * References:
+ *  - <A HREF="http://www.ietf.org/rfc/rfc4028.txt">RFC 4028: Session Timers 
+ *    in the Session Initiation Protocol (SIP)</A>
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * Opaque declaration of Session Timers.
+ */
+typedef struct pjsip_timer pjsip_timer;
+
+
+/**
+ * This structure describes Session Timers settings in an invite session.
+ */
+typedef struct pjsip_timer_setting
+{
+    /** 
+     * Specify minimum session expiration period, in seconds. Must not be
+     * lower than 90. Default is 90.
+     */
+    unsigned			 min_se;
+
+    /**
+     * Specify session expiration period, in seconds. Must not be lower than
+     * #min_se. Default is 1800.
+     */
+    unsigned			 sess_expires;	
+
+} pjsip_timer_setting;
+
+
+/**
+ * SIP Session-Expires header (RFC 4028).
+ */
+typedef struct pjsip_sess_expires_hdr
+{
+    /** Standard header field. */
+    PJSIP_DECL_HDR_MEMBER(struct pjsip_sess_expires_hdr);
+
+    /** Session expiration period */
+    unsigned	sess_expires;
+
+    /** Refresher */
+    pj_str_t	refresher;
+
+    /** Other parameters */
+    pjsip_param	other_param;
+
+} pjsip_sess_expires_hdr;
+
+
+/**
+ * SIP Min-SE header (RFC 4028).
+ */
+typedef struct pjsip_min_se_hdr
+{
+    /** Standard header field. */
+    PJSIP_DECL_HDR_MEMBER(struct pjsip_min_se_hdr);
+
+    /** Minimum session expiration period */
+    unsigned	min_se;
+
+    /** Other parameters */
+    pjsip_param	other_param;
+
+} pjsip_min_se_hdr;
+
+
+
+/**
+ * Initialize Session Timers module. This function must be called once during
+ * application initialization, to register this module to SIP endpoint.
+ *
+ * @param endpt		The SIP endpoint instance.
+ *
+ * @return		PJ_SUCCESS if module is successfully initialized.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt);
+
+
+/**
+ * Initialize Session Timers setting with default values.
+ *
+ * @param setting	Session Timers setting to be initialized.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_default_setting(pjsip_timer_setting *setting);
+
+
+/**
+ * Initialize Session Timers for an invite session. This function should be
+ * called by application to apply Session Timers setting, otherwise invite
+ * session will apply default setting to the Session Timers.
+ *
+ * @param inv		The invite session.
+ * @param setting	Session Timers setting, see @pjsip_timer_setting.
+ *			If setting is NULL, default setting will be applied.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_init_session(
+					pjsip_inv_session *inv,
+					const pjsip_timer_setting *setting);
+
+
+/**
+ * Create Session-Expires header.
+ *
+ * @param pool		Pool to allocate the header instance from.
+ *
+ * @return		An empty Session-Expires header instance.
+ */
+PJ_DECL(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create(
+							pj_pool_t *pool);
+
+
+/**
+ * Create Min-SE header.
+ *
+ * @param pool		Pool to allocate the header instance from.
+ *
+ * @return		An empty Min-SE header instance.
+ */
+PJ_DECL(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool);
+
+
+/**
+ * Update outgoing request to insert Session Timers headers and also
+ * signal Session Timers capability in Supported and/or Require headers.
+ *
+ * This function will be called internally by the invite session if it 
+ * detects that the session needs Session Timers support.
+ *
+ * @param inv		The invite session.
+ * @param tdata		Outgoing INVITE or UPDATE request.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv,
+					    pjsip_tx_data *tdata);
+
+
+/**
+ * Process Session Timers headers in incoming response, this function
+ * will only process incoming response with status code 422 (Session
+ * Interval Too Small) or 2xx (final response).
+ *
+ * This function will be called internally by the invite session if it 
+ * detects that the session needs Session Timers support.
+ *
+ * @param inv		The invite session.
+ * @param rdata		Incoming response data.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv,
+					      const pjsip_rx_data *rdata);
+
+
+/**
+ * Process Session Timers headers in incoming request, this function
+ * will only process incoming INVITE and UPDATE request.
+ *
+ * This function will be called internally by the invite session if it 
+ * detects that the session needs Session Timers support.
+ *
+ * @param inv		The invite session.
+ * @param rdata		Incoming INVITE or UPDATE request.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv,
+					     const pjsip_rx_data *rdata);
+
+
+/**
+ * Update outgoing response to insert Session Timers headers and also
+ * signal Session Timers capability in Supported and/or Require headers.
+ * This function will only update outgoing response with status code 
+ * 422 (Session Interval Too Small) or 2xx (final response).
+ *
+ * This function will be called internally by the invite session if it 
+ * detects that the session needs Session Timers support.
+ *
+ * @param inv		The invite session.
+ * @param tdata		Outgoing 422/2xx response.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv,
+					     pjsip_tx_data *tdata);
+
+/**
+ * End Session Timers in an invite session.
+ *
+ * This function will be called internally by the invite session if it 
+ * detects that the session needs Session Timers support.
+ *
+ * @param inv		The invite session.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv);
+
+
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJSIP_TIMER_H__ */
diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h
index 293c3c1..22eb061 100644
--- a/pjsip/include/pjsip/sip_config.h
+++ b/pjsip/include/pjsip/sip_config.h
@@ -657,7 +657,7 @@
 #define PJSIP_POOL_INC_DIALOG		512
 
 /* Maximum header types. */
-#define PJSIP_MAX_HEADER_TYPES		64
+#define PJSIP_MAX_HEADER_TYPES		72
 
 /* Maximum URI types. */
 #define PJSIP_MAX_URI_TYPES		4
diff --git a/pjsip/include/pjsip_ua.h b/pjsip/include/pjsip_ua.h
index 57f95ff..ce519f8 100644
--- a/pjsip/include/pjsip_ua.h
+++ b/pjsip/include/pjsip_ua.h
@@ -25,6 +25,7 @@
 #include <pjsip-ua/sip_replaces.h>
 #include <pjsip-ua/sip_xfer.h>
 #include <pjsip-ua/sip_100rel.h>
+#include <pjsip-ua/sip_timer.h>
 
 
 #endif	/* __PJSIP_UA_H__ */
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index e06019d..0b257c7 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -926,6 +926,33 @@
      */
     pj_bool_t	    require_100rel;
 
+    /**
+     * Specify whether support for Session Timers should be required by 
+     * default. Note that this setting can be further customized in account
+     * configuration (#pjsua_acc_config).
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t	    require_timer;
+
+    /**
+     * Specify session expiration period of Session Timers, in seconds. 
+     * Note that this setting can be further customized in account 
+     * configuration (#pjsua_acc_config).
+     *
+     * Default: 1800 (seconds)
+     */
+    unsigned	    timer_se;
+
+    /**
+     * Specify minimum session expiration period of Session Timers, 
+     * in seconds. Note that this setting can be further customized in 
+     * account configuration (#pjsua_acc_config).
+     *
+     * Default: 90 (seconds)
+     */
+    unsigned	    timer_min_se;
+
     /** 
      * Number of credentials in the credential array.
      */
@@ -1698,6 +1725,30 @@
     pj_bool_t	    require_100rel;
 
     /**
+     * Specify whether support for Session Timers should be required for all 
+     * sessions of this account.
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t	    require_timer;
+
+    /**
+     * Specify session expiration period of Session Timers, in seconds,
+     * for this account. 
+     *
+     * Default: 1800 (seconds)
+     */
+    unsigned	    timer_se;
+
+    /**
+     * Specify minimum session expiration period of Session Timers, 
+     * in seconds, for this account.
+     *
+     * Default: 90 (seconds)
+     */
+    unsigned	    timer_min_se;
+
+    /**
      * Number of proxies in the proxy array below.
      */
     unsigned	    proxy_cnt;
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index ae1011e..80ae971 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -19,6 +19,7 @@
  */
 #include <pjsip-ua/sip_inv.h>
 #include <pjsip-ua/sip_100rel.h>
+#include <pjsip-ua/sip_timer.h>
 #include <pjsip/sip_module.h>
 #include <pjsip/sip_endpoint.h>
 #include <pjsip/sip_event.h>
@@ -231,6 +232,7 @@
 	    inv->invite_req = NULL;
 	}
 	pjsip_100rel_end_session(inv);
+	pjsip_timer_end_session(inv);
 	pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
     }
 }
@@ -481,6 +483,7 @@
     pjsip_dialog *dlg;
     pjsip_inv_session *inv;
     pjsip_msg *msg = rdata->msg_info.msg;
+    pj_status_t status;
 
     dlg = pjsip_rdata_get_dlg(rdata);
 
@@ -508,6 +511,23 @@
 
     }
 
+    /* Pass response to timer session module */
+    status = pjsip_timer_process_resp(inv, rdata);
+    if (status != PJ_SUCCESS) {
+	pjsip_event e;
+	pjsip_tx_data *tdata;
+
+	PJSIP_EVENT_INIT_RX_MSG(e, rdata);
+	inv_send_ack(inv, &e);
+
+	status = pjsip_inv_end_session(inv, PJSIP_ERRNO_TO_SIP_STATUS(status),
+				       NULL, &tdata);
+	if (tdata && status == PJ_SUCCESS)
+	    pjsip_inv_send_msg(inv, tdata);
+
+	return PJ_TRUE;
+    }
+
     /* No other processing needs to be done here. */
     return PJ_FALSE;
 }
@@ -634,7 +654,6 @@
     /* Normalize options */
     if (options & PJSIP_INV_REQUIRE_100REL)
 	options |= PJSIP_INV_SUPPORT_100REL;
-
     if (options & PJSIP_INV_REQUIRE_TIMER)
 	options |= PJSIP_INV_SUPPORT_TIMER;
 
@@ -716,7 +735,6 @@
     /* Normalize options */
     if (*options & PJSIP_INV_REQUIRE_100REL)
 	*options |= PJSIP_INV_SUPPORT_100REL;
-
     if (*options & PJSIP_INV_REQUIRE_TIMER)
 	*options |= PJSIP_INV_SUPPORT_TIMER;
 
@@ -867,13 +885,13 @@
 	      pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);
     if (sup_hdr) {
 	unsigned i;
-	pj_str_t STR_100REL = { "100rel", 6};
-	pj_str_t STR_TIMER = { "timer", 5 };
+	const pj_str_t STR_100REL = { "100rel", 6};
+	const pj_str_t STR_TIMER = { "timer", 5};
 
 	for (i=0; i<sup_hdr->count; ++i) {
 	    if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0)
 		rem_option |= PJSIP_INV_SUPPORT_100REL;
-	    else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0)
+	    if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0)
 		rem_option |= PJSIP_INV_SUPPORT_TIMER;
 	}
     }
@@ -884,8 +902,8 @@
     if (req_hdr) {
 	unsigned i;
 	const pj_str_t STR_100REL = { "100rel", 6};
-	const pj_str_t STR_TIMER = { "timer", 5 };
 	const pj_str_t STR_REPLACES = { "replaces", 8 };
+	const pj_str_t STR_TIMER = { "timer", 5 };
 	unsigned unsupp_cnt = 0;
 	pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT];
 	
@@ -895,8 +913,8 @@
 	    {
 		rem_option |= PJSIP_INV_REQUIRE_100REL;
 
-	    } else if ((*options && PJSIP_INV_SUPPORT_TIMER) &&
-		       pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0)
+	    } else if ((*options & PJSIP_INV_SUPPORT_TIMER) && 
+		pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0)
 	    {
 		rem_option |= PJSIP_INV_REQUIRE_TIMER;
 
@@ -956,8 +974,8 @@
      */
     if ( ((*options & PJSIP_INV_REQUIRE_100REL)!=0 && 
 	  (rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
-	 ((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
-	  (rem_option & PJSIP_INV_SUPPORT_TIMER)==0))
+	 ((*options & PJSIP_INV_REQUIRE_100REL)!=0 && 
+	  (rem_option & PJSIP_INV_SUPPORT_100REL)==0))
     {
 	code = PJSIP_SC_EXTENSION_REQUIRED;
 	status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
@@ -971,7 +989,6 @@
 
 	    if (*options & PJSIP_INV_REQUIRE_100REL)
 		req_hdr->values[req_hdr->count++] = pj_str("100rel");
-
 	    if (*options & PJSIP_INV_REQUIRE_TIMER)
 		req_hdr->values[req_hdr->count++] = pj_str("timer");
 
@@ -1097,7 +1114,6 @@
     /* Normalize options */
     if (options & PJSIP_INV_REQUIRE_100REL)
 	options |= PJSIP_INV_SUPPORT_100REL;
-
     if (options & PJSIP_INV_REQUIRE_TIMER)
 	options |= PJSIP_INV_SUPPORT_TIMER;
 
@@ -1169,7 +1185,7 @@
 
     /* Create 100rel handler */
     if (inv->options & PJSIP_INV_REQUIRE_100REL) {
-	    pjsip_100rel_attach(inv);
+	pjsip_100rel_attach(inv);
     }
 
     /* Done */
@@ -1394,16 +1410,25 @@
     }
 
     /* Add Require header. */
-    if (inv->options & PJSIP_INV_REQUIRE_100REL) {
-	    const pj_str_t HREQ = { "Require", 7 };
-	    const pj_str_t tag_100rel = { "100rel", 6 };
-	    pjsip_generic_string_hdr *hreq;
+    if ((inv->options & PJSIP_INV_REQUIRE_100REL) ||
+	(inv->options & PJSIP_INV_REQUIRE_TIMER)) 
+    {
+	pjsip_require_hdr *hreq;
 
-	    hreq = pjsip_generic_string_hdr_create(tdata->pool, &HREQ, 
-						   &tag_100rel);
-	    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
+	hreq = pjsip_require_hdr_create(tdata->pool);
+
+	if (inv->options & PJSIP_INV_REQUIRE_100REL)
+	    hreq->values[hreq->count++] = pj_str("100rel");
+	if (inv->options & PJSIP_INV_REQUIRE_TIMER)
+	    hreq->values[hreq->count++] = pj_str("timer");
+
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
     }
 
+    status = pjsip_timer_update_req(inv, tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
     /* Done. */
     *p_tdata = tdata;
 
@@ -1745,6 +1770,27 @@
     if (status != PJ_SUCCESS)
 	goto on_return;
 
+    /* Invoke Session Timers module */
+    status = pjsip_timer_process_req(inv, rdata);
+    if (status != PJ_SUCCESS) {
+	pj_status_t status2;
+
+	status2 = pjsip_dlg_modify_response(inv->dlg, tdata, 
+					    PJSIP_ERRNO_TO_SIP_STATUS(status),
+					    NULL);
+	if (status2 != PJ_SUCCESS) {
+	    pjsip_tx_data_dec_ref(tdata);
+	    goto on_return;
+	}
+	status2 = pjsip_timer_update_resp(inv, tdata);
+	if (status2 == PJ_SUCCESS)
+	    *p_tdata = tdata;
+	else
+	    pjsip_tx_data_dec_ref(tdata);
+
+	goto on_return;
+    }
+
     /* Process SDP in answer */
     status = process_answer(inv, st_code, tdata, sdp);
     if (status != PJ_SUCCESS) {
@@ -1758,6 +1804,9 @@
     PJ_LOG(5,(inv->dlg->obj_name, "Initial answer %s",
 	      pjsip_tx_data_get_info(inv->last_answer)));
 
+    /* Invoke Session Timers */
+    pjsip_timer_update_resp(inv, tdata);
+
     *p_tdata = tdata;
 
 on_return:
@@ -1808,6 +1857,8 @@
 	goto on_return;
     }
 
+    /* Invoke Session Timers */
+    pjsip_timer_update_resp(inv, last_res);
 
     *p_tdata = last_res;
 
@@ -1912,6 +1963,9 @@
 
     case PJSIP_INV_STATE_CONNECTING:
     case PJSIP_INV_STATE_CONFIRMED:
+	/* End Session Timer */
+	pjsip_timer_end_session(inv);
+
 	/* For established dialog, send BYE */
 	status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(), 
 					  -1, &tdata);
@@ -2300,6 +2354,10 @@
     /* Unlock dialog. */
     pjsip_dlg_dec_lock(inv->dlg);
 
+    status = pjsip_timer_update_req(inv, tdata);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
     *p_tdata = tdata;
 
     return PJ_SUCCESS;
@@ -2581,6 +2639,15 @@
     pj_status_t status;
     pjsip_tx_data *tdata = NULL;
 
+    /* Invoke Session Timers module */
+    status = pjsip_timer_process_req(inv, rdata);
+    if (status != PJ_SUCCESS) {
+	status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					   PJSIP_ERRNO_TO_SIP_STATUS(status),
+					   NULL, &tdata);
+	goto on_return;
+    }
+
     neg_state = pjmedia_sdp_neg_get_state(inv->neg);
 
     /* Send 491 if we receive UPDATE while we're waiting for an answer */
@@ -2634,6 +2701,11 @@
 	}
     }
 
+on_return:
+    /* Invoke Session Timers */
+    if (status == PJ_SUCCESS)
+	status = pjsip_timer_update_resp(inv, tdata);
+
     if (status != PJ_SUCCESS) {
 	if (tdata != NULL) {
 	    pjsip_tx_data_dec_ref(tdata);
@@ -3609,6 +3681,20 @@
 	    /* Save the invite transaction. */
 	    inv->invite_tsx = tsx;
 
+	    /* Process session timers headers in the re-INVITE */
+	    status = pjsip_timer_process_req(inv, rdata);
+	    if (status != PJ_SUCCESS) {
+		status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					   PJSIP_ERRNO_TO_SIP_STATUS(status), 
+					   NULL, &tdata);
+		if (status != PJ_SUCCESS)
+		    return;
+
+		pjsip_timer_update_resp(inv, tdata);
+		status = pjsip_dlg_send_response(dlg, tsx, tdata);
+		return;
+	    }
+
 	    /* Process SDP in incoming message. */
 	    status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata);
 
@@ -3716,6 +3802,9 @@
 		return;
 	    }
 
+	    /* Invoke Session Timers */
+	    pjsip_timer_update_resp(inv, tdata);
+
 	    /* Send 2xx regardless of the status of negotiation */
 	    status = pjsip_inv_send_msg(inv, tdata);
 
diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c
new file mode 100644
index 0000000..628e0e2
--- /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;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 19d516b..013a249 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -479,8 +479,11 @@
 
     /* Create the INVITE session: */
     options |= PJSIP_INV_SUPPORT_100REL;
+    options |= PJSIP_INV_SUPPORT_TIMER;
     if (acc->cfg.require_100rel)
 	options |= PJSIP_INV_REQUIRE_100REL;
+    if (acc->cfg.require_timer)
+	options |= PJSIP_INV_REQUIRE_TIMER;
 
     status = pjsip_inv_create_uac( dlg, offer, options, &inv);
     if (status != PJ_SUCCESS) {
@@ -488,6 +491,20 @@
 	goto on_error;
     }
 
+    /* Init Session Timers */
+    {
+	pjsip_timer_setting timer_setting;
+
+	pjsip_timer_default_setting(&timer_setting);
+	timer_setting.sess_expires = acc->cfg.timer_se;
+	timer_setting.min_se = acc->cfg.timer_min_se;
+
+	status = pjsip_timer_init_session(inv, &timer_setting);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Session Timer init failed", status);
+	    goto on_error;
+	}
+    }
 
     /* Create and associate our data in the session. */
     call->acc_id = acc_id;
@@ -805,8 +822,11 @@
 
     /* Verify that we can handle the request. */
     options |= PJSIP_INV_SUPPORT_100REL;
+    options |= PJSIP_INV_SUPPORT_TIMER;
     if (pjsua_var.acc[acc_id].cfg.require_100rel)
 	options |= PJSIP_INV_REQUIRE_100REL;
+    if (pjsua_var.acc[acc_id].cfg.require_timer)
+	options |= PJSIP_INV_REQUIRE_TIMER;
 
     status = pjsip_inv_verify_request2(rdata, &options, offer, answer, NULL,
 				       pjsua_var.endpt, &response);
@@ -895,6 +915,29 @@
 	return PJ_TRUE;
     }
 
+    /* Init Session Timers */
+    {
+	pjsip_timer_setting timer_setting;
+
+	pjsip_timer_default_setting(&timer_setting);
+	timer_setting.sess_expires = pjsua_var.acc[acc_id].cfg.timer_se;
+	timer_setting.min_se = pjsua_var.acc[acc_id].cfg.timer_min_se;
+
+	status = pjsip_timer_init_session(inv, &timer_setting);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Session Timer init failed", status);
+	    status = pjsip_inv_end_session(inv, PJSIP_SC_INTERNAL_SERVER_ERROR,
+					   NULL, &response);
+	    if (status == PJ_SUCCESS && response)
+		status = pjsip_inv_send_msg(inv, response);
+
+	    pjsua_media_channel_deinit(call->index);
+
+	    PJSUA_UNLOCK();
+	    return PJ_TRUE;
+	}
+    }
+
     /* Update NAT type of remote endpoint, only when there is SDP in
      * incoming INVITE! 
      */
@@ -928,11 +971,16 @@
     status = pjsip_inv_initial_answer(inv, rdata, 
 				      100, NULL, NULL, &response);
     if (status != PJ_SUCCESS) {
-	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);
+	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);
+	} else {
+	    pjsip_inv_send_msg(inv, response);
+	    pjsip_inv_terminate(inv, PJSIP_ERRNO_TO_SIP_STATUS(status), 
+				PJ_FALSE);
+	}
 	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 30ae00e..6cd9cac 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -87,6 +87,8 @@
 
 PJ_DEF(void) pjsua_config_default(pjsua_config *cfg)
 {
+    pjsip_timer_setting timer_setting;
+
     pj_bzero(cfg, sizeof(*cfg));
 
     cfg->max_calls = ((PJSUA_MAX_CALLS) < 4) ? (PJSUA_MAX_CALLS) : 4;
@@ -98,6 +100,10 @@
     cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING;
 #endif
     cfg->hangup_forked_call = PJ_TRUE;
+
+    pjsip_timer_default_setting(&timer_setting);
+    cfg->timer_se = timer_setting.sess_expires;
+    cfg->timer_min_se = timer_setting.min_se;
 }
 
 PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool,
@@ -150,6 +156,9 @@
     cfg->transport_id = PJSUA_INVALID_ID;
     cfg->allow_contact_rewrite = PJ_TRUE;
     cfg->require_100rel = pjsua_var.ua_cfg.require_100rel;
+    cfg->require_timer = pjsua_var.ua_cfg.require_timer;
+    cfg->timer_se = pjsua_var.ua_cfg.timer_se;
+    cfg->timer_min_se = pjsua_var.ua_cfg.timer_min_se;
     cfg->ka_interval = 15;
     cfg->ka_data = pj_str("\r\n");
 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
@@ -721,6 +730,10 @@
     status = pjsip_100rel_init_module(pjsua_var.endpt);
     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
 
+    /* Initialize session timer support */
+    status = pjsip_timer_init_module(pjsua_var.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
     /* Initialize and register PJSUA application module. */
     {
 	const pjsip_module mod_initializer =