blob: 34676e0df4b3c881eafbf7af4fe399cf550fbde5 [file] [log] [blame]
Nanang Izzuddin59dffb12009-08-11 12:42:38 +00001/* $Id$ */
2/*
3 * Copyright (C) 2009 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsip-ua/sip_timer.h>
20#include <pjsip/print_util.h>
21#include <pjsip/sip_endpoint.h>
22#include <pj/log.h>
23#include <pj/math.h>
24#include <pj/os.h>
25#include <pj/pool.h>
26
27#define THIS_FILE "sip_timer.c"
28
29
Nanang Izzuddin65add622009-08-11 16:26:20 +000030/* Constant of Session Timers */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000031#define ABS_MIN_SE 90 /* Absolute Min-SE, in seconds */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000032
33
34/* String definitions */
35static const pj_str_t STR_SE = {"Session-Expires", 15};
36static const pj_str_t STR_SHORT_SE = {"x", 1};
37static const pj_str_t STR_MIN_SE = {"Min-SE", 6};
38static const pj_str_t STR_REFRESHER = {"refresher", 9};
39static const pj_str_t STR_UAC = {"uac", 3};
40static const pj_str_t STR_UAS = {"uas", 3};
41static const pj_str_t STR_TIMER = {"timer", 5};
42
43
44/* Enumeration of refresher */
45enum timer_refresher {
46 TR_UNKNOWN,
47 TR_UAC,
48 TR_UAS
49};
50
51/* Structure definition of Session Timers */
Nanang Izzuddin91ce6e42009-08-12 11:23:39 +000052struct pjsip_timer
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000053{
54 pj_bool_t active; /**< Active/inactive flag */
55 pjsip_timer_setting setting; /**< Session Timers setting */
56 enum timer_refresher refresher; /**< Session refresher */
57 pj_time_val last_refresh; /**< Timestamp of last
58 refresh */
59 pj_timer_entry timer; /**< Timer entry */
60 pj_bool_t use_update; /**< Use UPDATE method to
61 refresh the session */
Nanang Izzuddin65add622009-08-11 16:26:20 +000062 pjsip_role_e role; /**< Role in last INVITE/
63 UPDATE transaction. */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000064
Nanang Izzuddin91ce6e42009-08-12 11:23:39 +000065};
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000066
67/* External global vars */
68extern pj_bool_t pjsip_use_compact_form;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000069
70/* Local functions & vars */
71static void stop_timer(pjsip_inv_session *inv);
72static void start_timer(pjsip_inv_session *inv);
73static pj_bool_t is_initialized;
Nanang Izzuddin9b93f862009-08-11 18:21:13 +000074const pjsip_method pjsip_update_method = { PJSIP_OTHER_METHOD, {"UPDATE", 6}};
Nanang Izzuddin59dffb12009-08-11 12:42:38 +000075/*
76 * Session-Expires header vptr.
77 */
78static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
79 char *buf, pj_size_t size);
80static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
81 const pjsip_sess_expires_hdr *hdr);
82static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
83 pj_pool_t *pool,
84 const pjsip_sess_expires_hdr* hdr);
85
86static pjsip_hdr_vptr se_hdr_vptr =
87{
88 (pjsip_hdr_clone_fptr) &se_hdr_clone,
89 (pjsip_hdr_clone_fptr) &se_hdr_shallow_clone,
90 (pjsip_hdr_print_fptr) &se_hdr_print,
91};
92
93/*
94 * Min-SE header vptr.
95 */
96static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
97 char *buf, pj_size_t size);
98static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
99 const pjsip_min_se_hdr *hdr);
100static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
101 pj_pool_t *pool,
102 const pjsip_min_se_hdr* hdr);
103
104static pjsip_hdr_vptr min_se_hdr_vptr =
105{
106 (pjsip_hdr_clone_fptr) &min_se_hdr_clone,
107 (pjsip_hdr_clone_fptr) &min_se_hdr_shallow_clone,
108 (pjsip_hdr_print_fptr) &min_se_hdr_print,
109};
110
111/*
112 * Session-Expires header vptr.
113 */
114static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
115 char *buf, pj_size_t size)
116{
117 char *p = buf;
118 char *endbuf = buf+size;
119 int printed;
120 const pjsip_parser_const_t *pc = pjsip_parser_const();
121 const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
122
Nanang Izzuddin65add622009-08-11 16:26:20 +0000123 /* Print header name and value */
124 if ((endbuf - p) < (hname->slen + 16))
125 return -1;
126
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000127 copy_advance(p, (*hname));
128 *p++ = ':';
129 *p++ = ' ';
130
131 printed = pj_utoa(hdr->sess_expires, p);
132 p += printed;
133
Nanang Izzuddin65add622009-08-11 16:26:20 +0000134 /* Print 'refresher' param */
135 if (hdr->refresher.slen)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000136 {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000137 if ((endbuf - p) < (STR_REFRESHER.slen + 2 + hdr->refresher.slen))
138 return -1;
139
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000140 *p++ = ';';
141 copy_advance(p, STR_REFRESHER);
142 *p++ = '=';
143 copy_advance(p, hdr->refresher);
144 }
145
Nanang Izzuddin65add622009-08-11 16:26:20 +0000146 /* Print generic params */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000147 printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
148 &pc->pjsip_TOKEN_SPEC,
149 &pc->pjsip_TOKEN_SPEC, ';');
150 if (printed < 0)
151 return printed;
152
153 p += printed;
154 return p - buf;
155}
156
157static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
158 const pjsip_sess_expires_hdr *hsrc)
159{
160 pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(pool);
161 hdr->sess_expires = hsrc->sess_expires;
162 pj_strdup(pool, &hdr->refresher, &hsrc->refresher);
163 pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
164 return hdr;
165}
166
167static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
168 pj_pool_t *pool,
169 const pjsip_sess_expires_hdr* hsrc)
170{
171 pjsip_sess_expires_hdr *hdr = PJ_POOL_ALLOC_T(pool,pjsip_sess_expires_hdr);
172 pj_memcpy(hdr, hsrc, sizeof(*hdr));
173 pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
174 return hdr;
175}
176
177/*
178 * Min-SE header vptr.
179 */
180static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
181 char *buf, pj_size_t size)
182{
183 char *p = buf;
184 char *endbuf = buf+size;
185 int printed;
186 const pjsip_parser_const_t *pc = pjsip_parser_const();
187
Nanang Izzuddin65add622009-08-11 16:26:20 +0000188 /* Print header name and value */
189 if ((endbuf - p) < (hdr->name.slen + 16))
190 return -1;
191
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000192 copy_advance(p, hdr->name);
193 *p++ = ':';
194 *p++ = ' ';
195
196 printed = pj_utoa(hdr->min_se, p);
197 p += printed;
198
Nanang Izzuddin65add622009-08-11 16:26:20 +0000199 /* Print generic params */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000200 printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
201 &pc->pjsip_TOKEN_SPEC,
202 &pc->pjsip_TOKEN_SPEC, ';');
203 if (printed < 0)
204 return printed;
205
206 p += printed;
207 return p - buf;
208}
209
210static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
211 const pjsip_min_se_hdr *hsrc)
212{
213 pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(pool);
214 hdr->min_se = hsrc->min_se;
215 pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
216 return hdr;
217}
218
219static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
220 pj_pool_t *pool,
221 const pjsip_min_se_hdr* hsrc)
222{
223 pjsip_min_se_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_min_se_hdr);
224 pj_memcpy(hdr, hsrc, sizeof(*hdr));
225 pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
226 return hdr;
227}
228
229
230/*
231 * Parse Session-Expires header.
232 */
233static pjsip_hdr *parse_hdr_se(pjsip_parse_ctx *ctx)
234{
235 pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(ctx->pool);
236 const pjsip_parser_const_t *pc = pjsip_parser_const();
237 pj_str_t token;
238
239 pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
240 hdr->sess_expires = pj_strtoul(&token);
241
242 while (*ctx->scanner->curptr == ';') {
243 pj_str_t pname, pvalue;
244
245 pj_scan_get_char(ctx->scanner);
246 pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
247
248 if (pj_stricmp(&pname, &STR_REFRESHER)==0) {
249 hdr->refresher = pvalue;
250 } else {
251 pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
252 param->name = pname;
253 param->value = pvalue;
254 pj_list_push_back(&hdr->other_param, param);
255 }
256 }
257 pjsip_parse_end_hdr_imp( ctx->scanner );
258 return (pjsip_hdr*)hdr;
259}
260
261/*
262 * Parse Min-SE header.
263 */
264static pjsip_hdr *parse_hdr_min_se(pjsip_parse_ctx *ctx)
265{
266 pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(ctx->pool);
267 const pjsip_parser_const_t *pc = pjsip_parser_const();
268 pj_str_t token;
269
270 pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
271 hdr->min_se = pj_strtoul(&token);
272
273 while (*ctx->scanner->curptr == ';') {
274 pj_str_t pname, pvalue;
275 pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
276
277 pj_scan_get_char(ctx->scanner);
278 pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
279
280 param->name = pname;
281 param->value = pvalue;
282 pj_list_push_back(&hdr->other_param, param);
283 }
284 pjsip_parse_end_hdr_imp( ctx->scanner );
285 return (pjsip_hdr*)hdr;
286}
287
288
289/* Add "Session-Expires" and "Min-SE" headers. Note that "Min-SE" header
290 * can only be added to INVITE/UPDATE request and 422 response.
291 */
292static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata,
293 pj_bool_t add_se, pj_bool_t add_min_se)
294{
295 pjsip_timer *timer = inv->timer;
296
297 /* Add Session-Expires header */
298 if (add_se) {
299 pjsip_sess_expires_hdr *hdr;
300
301 hdr = pjsip_sess_expires_hdr_create(tdata->pool);
302 hdr->sess_expires = timer->setting.sess_expires;
303 if (timer->refresher != TR_UNKNOWN)
304 hdr->refresher = (timer->refresher == TR_UAC? STR_UAC : STR_UAS);
305
306 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
307 }
308
309 /* Add Min-SE header */
310 if (add_min_se) {
311 pjsip_min_se_hdr *hdr;
312
313 hdr = pjsip_min_se_hdr_create(tdata->pool);
314 hdr->min_se = timer->setting.min_se;
315
316 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
317 }
318}
319
320/* Timer callback. When the timer is fired, it can be time to refresh
321 * the session if UA is the refresher, otherwise it is time to end
322 * the session.
323 */
324void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
325{
326 pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data;
327 pjsip_tx_data *tdata = NULL;
328 pj_status_t status;
329 pj_bool_t as_refresher;
330
331 pj_assert(inv);
332
333 PJ_UNUSED_ARG(timer_heap);
334
Nanang Izzuddin71a20b12009-08-17 14:42:33 +0000335 /* When there is a pending INVITE transaction, delay/reschedule this timer
336 * for five seconds to cover the case that pending INVITE fails and the
337 * previous session is still active. If the pending INVITE is successful,
338 * timer state will be updated, i.e: restarted or stopped.
339 */
340 if (inv->invite_tsx != NULL) {
341 pj_time_val delay = {5};
342
343 inv->timer->timer.id = 1;
344 pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, &delay);
Nanang Izzuddinbcc57682009-08-17 12:59:21 +0000345 return;
Nanang Izzuddin71a20b12009-08-17 14:42:33 +0000346 }
Nanang Izzuddinbcc57682009-08-17 12:59:21 +0000347
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000348 /* Lock dialog. */
349 pjsip_dlg_inc_lock(inv->dlg);
350
351 /* Check our role */
352 as_refresher =
Nanang Izzuddin65add622009-08-11 16:26:20 +0000353 (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
354 (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS);
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000355
356 /* Do action based on role, refresher or refreshee */
357 if (as_refresher) {
358
359 pj_time_val now;
360
361 /* Refresher, refresh the session */
362 if (inv->timer->use_update) {
363 /* Create UPDATE request without offer */
364 status = pjsip_inv_update(inv, NULL, NULL, &tdata);
365 } else {
366 /* Create re-INVITE without modifying session */
367 pjsip_msg_body *body;
368 const pjmedia_sdp_session *offer = NULL;
369
370 pj_assert(pjmedia_sdp_neg_get_state(inv->neg) ==
371 PJMEDIA_SDP_NEG_STATE_DONE);
372
373 status = pjsip_inv_invite(inv, &tdata);
374 if (status == PJ_SUCCESS)
Benny Prijono40d62b62009-08-12 17:53:47 +0000375 status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov,
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000376 inv->neg, &offer);
377 if (status == PJ_SUCCESS)
378 status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
379 if (status == PJ_SUCCESS) {
380 status = pjsip_create_sdp_body(tdata->pool,
381 (pjmedia_sdp_session*)offer, &body);
382 tdata->msg->body = body;
383 }
384 }
385
386 pj_gettimeofday(&now);
387 PJ_LOG(4, (inv->pool->obj_name,
388 "Refresh session after %ds (expiration period=%ds)",
389 (now.sec-inv->timer->last_refresh.sec),
390 inv->timer->setting.sess_expires));
391 } else {
392
393 pj_time_val now;
394
395 /* Refreshee, terminate the session */
396 status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT,
397 NULL, &tdata);
398
399 pj_gettimeofday(&now);
400 PJ_LOG(3, (inv->pool->obj_name,
401 "No session refresh received after %ds "
402 "(expiration period=%ds), stopping session now!",
403 (now.sec-inv->timer->last_refresh.sec),
404 inv->timer->setting.sess_expires));
405 }
406
407 /* Unlock dialog. */
408 pjsip_dlg_dec_lock(inv->dlg);
409
410 /* Send message, if any */
411 if (tdata && status == PJ_SUCCESS) {
412 status = pjsip_inv_send_msg(inv, tdata);
413 }
414
415 /* Print error message, if any */
416 if (status != PJ_SUCCESS) {
417 char errmsg[PJ_ERR_MSG_SIZE];
418
419 if (tdata)
420 pjsip_tx_data_dec_ref(tdata);
421
422 pj_strerror(status, errmsg, sizeof(errmsg));
423 PJ_LOG(2, (inv->pool->obj_name, "Session timer fails in %s session, "
424 "err code=%d (%s)",
425 (as_refresher? "refreshing" :
426 "terminating"),
427 status, errmsg));
428 }
429}
430
431/* Start Session Timers */
432static void start_timer(pjsip_inv_session *inv)
433{
434 pjsip_timer *timer = inv->timer;
435 pj_time_val delay = {0};
436
437 pj_assert(inv->timer->active == PJ_TRUE);
438
439 stop_timer(inv);
440
441 pj_timer_entry_init(&timer->timer,
442 1, /* id */
443 inv, /* user data */
444 timer_cb); /* callback */
445
446 /* Set delay based on role, refresher or refreshee */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000447 if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
448 (timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000449 {
450 /* Next refresh, the delay is half of session expire */
451 delay.sec = timer->setting.sess_expires / 2;
452 } else {
453 /* Send BYE if no refresh received until this timer fired, delay
454 * is the minimum of 32 seconds and one third of the session interval
455 * before session expiration.
456 */
457 delay.sec = timer->setting.sess_expires -
458 timer->setting.sess_expires/3;
459 delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
460 }
461
462 /* Schedule the timer */
463 pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);
464
465 /* Update last refresh time */
466 pj_gettimeofday(&timer->last_refresh);
467}
468
469/* Stop Session Timers */
470static void stop_timer(pjsip_inv_session *inv)
471{
472 if (inv->timer->timer.id != 0) {
473 pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->timer);
474 inv->timer->timer.id = 0;
475 }
476}
477
Nanang Izzuddinf9680d22009-08-17 15:56:18 +0000478/* Deinitialize Session Timers */
479static void pjsip_timer_deinit_module(void)
480{
481 is_initialized = PJ_FALSE;
482}
483
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000484/*
485 * Initialize Session Timers support in PJSIP.
486 */
487PJ_DEF(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt)
488{
489 pj_status_t status;
490
491 PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
492
493 if (is_initialized)
494 return PJ_SUCCESS;
495
496 /* Register Session-Expires header parser */
497 status = pjsip_register_hdr_parser( STR_SE.ptr, STR_SHORT_SE.ptr,
498 &parse_hdr_se);
499 if (status != PJ_SUCCESS)
500 return status;
501
502 /* Register Min-SE header parser */
503 status = pjsip_register_hdr_parser( STR_MIN_SE.ptr, NULL,
504 &parse_hdr_min_se);
505 if (status != PJ_SUCCESS)
506 return status;
507
508 /* Register 'timer' capability to endpoint */
509 status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED,
510 NULL, 1, &STR_TIMER);
511 if (status != PJ_SUCCESS)
512 return status;
513
Nanang Izzuddinf9680d22009-08-17 15:56:18 +0000514 /* Register deinit module to be executed when PJLIB shutdown */
515 if (pj_atexit(&pjsip_timer_deinit_module) != PJ_SUCCESS) {
516 /* Failure to register this function may cause this module won't
517 * work properly when the stack is restarted (without quitting
518 * application).
519 */
520 pj_assert(!"Failed to register Session Timer deinit.");
521 PJ_LOG(1, (THIS_FILE, "Failed to register Session Timer deinit."));
522 }
523
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000524 is_initialized = PJ_TRUE;
525
526 return PJ_SUCCESS;
527}
528
529
530/*
531 * Initialize Session Timers setting with default values.
532 */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000533PJ_DEF(pj_status_t) pjsip_timer_setting_default(pjsip_timer_setting *setting)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000534{
535 pj_bzero(setting, sizeof(pjsip_timer_setting));
536
Nanang Izzuddin65add622009-08-11 16:26:20 +0000537 setting->sess_expires = PJSIP_SESS_TIMER_DEF_SE;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000538 setting->min_se = ABS_MIN_SE;
539
540 return PJ_SUCCESS;
541}
542
543/*
544 * Initialize Session Timers in an INVITE session.
545 */
546PJ_DEF(pj_status_t) pjsip_timer_init_session(
547 pjsip_inv_session *inv,
548 const pjsip_timer_setting *setting)
549{
550 pjsip_timer_setting *s;
551
552 pj_assert(is_initialized);
553 PJ_ASSERT_RETURN(inv, PJ_EINVAL);
554
555 /* Allocate and/or reset Session Timers structure */
556 if (!inv->timer)
557 inv->timer = PJ_POOL_ZALLOC_T(inv->pool, pjsip_timer);
558 else
559 pj_bzero(inv->timer, sizeof(pjsip_timer));
560
561 s = &inv->timer->setting;
562
563 /* Init Session Timers setting */
564 if (setting) {
565 PJ_ASSERT_RETURN(setting->min_se >= ABS_MIN_SE,
566 PJ_ETOOSMALL);
567 PJ_ASSERT_RETURN(setting->sess_expires >= setting->min_se,
568 PJ_EINVAL);
569
570 pj_memcpy(s, setting, sizeof(*s));
571 } else {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000572 pjsip_timer_setting_default(s);
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000573 }
574
575 return PJ_SUCCESS;
576}
577
578
579/*
580 * Create Session-Expires header.
581 */
582PJ_DEF(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create(
583 pj_pool_t *pool)
584{
585 pjsip_sess_expires_hdr *hdr = PJ_POOL_ZALLOC_T(pool,
586 pjsip_sess_expires_hdr);
587
588 pj_assert(is_initialized);
589
590 hdr->type = PJSIP_H_OTHER;
591 hdr->name = STR_SE;
592 hdr->sname = STR_SHORT_SE;
593 hdr->vptr = &se_hdr_vptr;
594 pj_list_init(hdr);
595 pj_list_init(&hdr->other_param);
596 return hdr;
597}
598
599
600/*
601 * Create Min-SE header.
602 */
603PJ_DEF(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool)
604{
605 pjsip_min_se_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_min_se_hdr);
606
607 pj_assert(is_initialized);
608
609 hdr->type = PJSIP_H_OTHER;
610 hdr->name = STR_MIN_SE;
611 hdr->vptr = &min_se_hdr_vptr;
612 pj_list_init(hdr);
613 pj_list_init(&hdr->other_param);
614 return hdr;
615}
616
617
618/*
619 * This function generates headers for Session Timers for intial and
620 * refresh INVITE or UPDATE.
621 */
622PJ_DEF(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv,
623 pjsip_tx_data *tdata)
624{
625 PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
626
627 /* Check if Session Timers is supported */
628 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
629 return PJ_SUCCESS;
630
631 pj_assert(is_initialized);
632
633 /* Make sure Session Timers is initialized */
634 if (inv->timer == NULL)
635 pjsip_timer_init_session(inv, NULL);
636
Nanang Izzuddinfc9efff2009-08-12 19:04:02 +0000637 /* If refresher role (i.e: ours or peer) has been set/negotiated,
638 * better to keep it.
639 */
640 if (inv->timer->refresher != TR_UNKNOWN) {
641 pj_bool_t as_refresher;
642
643 /* Check our refresher role */
644 as_refresher =
645 (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
646 (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
647
648 /* Update transaction role */
649 inv->timer->role = PJSIP_ROLE_UAC;
650
651 /* Update refresher role */
652 inv->timer->refresher = as_refresher? TR_UAC : TR_UAS;
653 }
654
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000655 /* Add Session Timers headers */
656 add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
657
658 return PJ_SUCCESS;
659}
660
661/*
662 * This function will handle Session Timers part of INVITE/UPDATE
663 * responses with code:
664 * - 422 (Session Interval Too Small)
665 * - 2xx final response
666 */
667PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv,
Nanang Izzuddin65add622009-08-11 16:26:20 +0000668 const pjsip_rx_data *rdata,
669 pjsip_status_code *st_code)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000670{
671 const pjsip_msg *msg;
672
Nanang Izzuddin65add622009-08-11 16:26:20 +0000673 PJ_ASSERT_ON_FAIL(inv && rdata,
674 {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000675
676 /* Check if Session Timers is supported */
677 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
678 return PJ_SUCCESS;
679
680 pj_assert(is_initialized);
681
682 msg = rdata->msg_info.msg;
683 pj_assert(msg->type == PJSIP_RESPONSE_MSG);
684
685 /* Only process response of INVITE or UPDATE */
686 if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD &&
687 pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
688 {
689 return PJ_SUCCESS;
690 }
691
692 if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) {
693 /* Our Session-Expires is too small, let's update it based on
694 * Min-SE header in the response.
695 */
696 pjsip_tx_data *tdata;
697 pjsip_min_se_hdr *min_se_hdr;
698 pjsip_hdr *hdr;
699 pjsip_via_hdr *via;
700
701 /* Get Min-SE value from response */
702 min_se_hdr = (pjsip_min_se_hdr*)
703 pjsip_msg_find_hdr_by_name(msg, &STR_MIN_SE, NULL);
704 if (min_se_hdr == NULL) {
705 /* Response 422 should contain Min-SE header */
706 return PJ_SUCCESS;
707 }
708
709 /* Session Timers should have been initialized here */
710 pj_assert(inv->timer);
711
712 /* Update Min-SE */
713 inv->timer->setting.min_se = PJ_MAX(min_se_hdr->min_se,
714 inv->timer->setting.min_se);
715
716 /* Update Session Timers setting */
717 if (inv->timer->setting.sess_expires < inv->timer->setting.min_se)
718 inv->timer->setting.sess_expires = inv->timer->setting.min_se;
719
720 /* Prepare to restart the request */
721
722 /* Get the original INVITE request. */
723 tdata = inv->invite_req;
724
725 /* Remove branch param in Via header. */
726 via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
727 pj_assert(via);
728 via->branch_param.slen = 0;
729
730 /* Restore strict route set.
731 * See http://trac.pjsip.org/repos/ticket/492
732 */
733 pjsip_restore_strict_route_set(tdata);
734
735 /* Must invalidate the message! */
736 pjsip_tx_data_invalidate_msg(tdata);
737
738 pjsip_tx_data_add_ref(tdata);
739
740 /* Update Session Timers headers */
741 hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg,
742 &STR_MIN_SE, NULL);
743 if (hdr != NULL) pj_list_erase(hdr);
744
745 hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_names(tdata->msg, &STR_SE,
746 &STR_SHORT_SE, NULL);
747 if (hdr != NULL) pj_list_erase(hdr);
748
749 add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
750
751 /* Restart UAC */
752 pjsip_inv_uac_restart(inv, PJ_FALSE);
753 pjsip_inv_send_msg(inv, tdata);
754
755 return PJ_SUCCESS;
756
757 } else if (msg->line.status.code/100 == 2) {
758
759 pjsip_sess_expires_hdr *se_hdr;
760
761 /* Find Session-Expires header */
762 se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
763 msg, &STR_SE,
764 &STR_SHORT_SE, NULL);
765 if (se_hdr == NULL) {
766 /* Remote doesn't support/want Session Timers, check if local
767 * require or force to use Session Timers.
768 */
769 if (inv->options & PJSIP_INV_REQUIRE_TIMER) {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000770 if (st_code)
771 *st_code = PJSIP_SC_EXTENSION_REQUIRED;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000772 pjsip_timer_end_session(inv);
773 return PJSIP_ERRNO_FROM_SIP_STATUS(
774 PJSIP_SC_EXTENSION_REQUIRED);
775 }
776
777 if ((inv->options & PJSIP_INV_ALWAYS_USE_TIMER) == 0) {
778 /* Session Timers not forced */
779 pjsip_timer_end_session(inv);
780 return PJ_SUCCESS;
781 }
782 }
783
784 /* Make sure Session Timers is initialized */
785 if (inv->timer == NULL)
786 pjsip_timer_init_session(inv, NULL);
787
788 /* Session expiration period specified by remote is lower than our
789 * Min-SE.
790 */
791 if (se_hdr &&
792 se_hdr->sess_expires < inv->timer->setting.min_se)
793 {
Nanang Izzuddin346d6ec2009-10-09 12:19:35 +0000794 /* See ticket #954, instead of returning non-PJ_SUCCESS (which
795 * may cause disconnecting call/dialog), let's just accept the
796 * SE and update our local SE, as long as it isn't less than 90s.
797 */
798 if (se_hdr->sess_expires >= ABS_MIN_SE) {
799 PJ_LOG(3, (inv->pool->obj_name,
800 "Peer responds with bad Session-Expires, %ds, "
801 "which is less than Min-SE specified in request, "
802 "%ds. Well, let's just accept and use it.",
803 se_hdr->sess_expires, inv->timer->setting.min_se));
804
805 inv->timer->setting.sess_expires = se_hdr->sess_expires;
Nanang Izzuddindedcdf02009-10-09 12:31:59 +0000806 inv->timer->setting.min_se = se_hdr->sess_expires;
Nanang Izzuddin346d6ec2009-10-09 12:19:35 +0000807 }
808
809 //if (st_code)
810 // *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
811 //pjsip_timer_end_session(inv);
812 //return PJSIP_ERRNO_FROM_SIP_STATUS(
813 // PJSIP_SC_SESSION_TIMER_TOO_SMALL);
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000814 }
815
816 /* Update SE. Session-Expires in response cannot be lower than Min-SE.
817 * Session-Expires in response can only be equal or lower than in
818 * request.
819 */
820 if (se_hdr &&
821 se_hdr->sess_expires <= inv->timer->setting.sess_expires &&
822 se_hdr->sess_expires >= inv->timer->setting.min_se)
823 {
824 /* Good SE from remote, update local SE */
825 inv->timer->setting.sess_expires = se_hdr->sess_expires;
826 }
827
828 /* Set the refresher */
829 if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
830 inv->timer->refresher = TR_UAC;
831 else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
832 inv->timer->refresher = TR_UAS;
833 else
834 /* UAS should set the refresher, however, there is a case that
835 * UAS doesn't support/want Session Timers but the UAC insists
836 * to use Session Timers.
837 */
838 inv->timer->refresher = TR_UAC;
839
840 PJ_TODO(CHECK_IF_REMOTE_SUPPORT_UPDATE);
841
Nanang Izzuddin65add622009-08-11 16:26:20 +0000842 /* Remember our role in this transaction */
843 inv->timer->role = PJSIP_ROLE_UAC;
844
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000845 /* Finally, set active flag and start the Session Timers */
846 inv->timer->active = PJ_TRUE;
847 start_timer(inv);
848 }
849
850 return PJ_SUCCESS;
851}
852
853/*
854 * Handle incoming INVITE or UPDATE request.
855 */
856PJ_DEF(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv,
Nanang Izzuddin65add622009-08-11 16:26:20 +0000857 const pjsip_rx_data *rdata,
858 pjsip_status_code *st_code)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000859{
860 pjsip_min_se_hdr *min_se_hdr;
861 pjsip_sess_expires_hdr *se_hdr;
862 const pjsip_msg *msg;
863 unsigned min_se;
864
Nanang Izzuddin65add622009-08-11 16:26:20 +0000865 PJ_ASSERT_ON_FAIL(inv && rdata,
866 {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000867
868 /* Check if Session Timers is supported */
869 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
870 return PJ_SUCCESS;
871
872 pj_assert(is_initialized);
873
874 msg = rdata->msg_info.msg;
875 pj_assert(msg->type == PJSIP_REQUEST_MSG);
876
877 /* Only process INVITE or UPDATE request */
878 if (msg->line.req.method.id != PJSIP_INVITE_METHOD &&
879 pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
880 {
881 return PJ_SUCCESS;
882 }
883
884 /* Find Session-Expires header */
885 se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
886 msg, &STR_SE, &STR_SHORT_SE, NULL);
887 if (se_hdr == NULL) {
888 /* Remote doesn't support/want Session Timers, check if local
889 * require or force to use Session Timers. Note that Supported and
890 * Require headers negotiation should have been verified by invite
891 * session.
892 */
893 if ((inv->options &
894 (PJSIP_INV_REQUIRE_TIMER | PJSIP_INV_ALWAYS_USE_TIMER)) == 0)
895 {
896 /* Session Timers not forced/required */
897 pjsip_timer_end_session(inv);
898 return PJ_SUCCESS;
899 }
900 }
901
902 /* Make sure Session Timers is initialized */
903 if (inv->timer == NULL)
904 pjsip_timer_init_session(inv, NULL);
905
906 /* Find Min-SE header */
907 min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(msg,
908 &STR_MIN_SE, NULL);
909 /* Update Min-SE */
910 min_se = inv->timer->setting.min_se;
911 if (min_se_hdr)
912 min_se = PJ_MAX(min_se_hdr->min_se, min_se);
913
914 /* Validate SE. Session-Expires cannot be lower than Min-SE
915 * (or 90 seconds if Min-SE is not set).
916 */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000917 if (se_hdr && se_hdr->sess_expires < min_se) {
918 if (st_code)
919 *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000920 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_SESSION_TIMER_TOO_SMALL);
Nanang Izzuddin65add622009-08-11 16:26:20 +0000921 }
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000922
923 /* Update SE. Note that there is a case that SE is not available in the
924 * request (which means remote doesn't want/support it), but local insists
925 * to use Session Timers.
926 */
927 if (se_hdr) {
928 /* Update SE as specified by peer. */
929 inv->timer->setting.sess_expires = se_hdr->sess_expires;
930 } else if (inv->timer->setting.sess_expires < min_se) {
931 /* There is no SE in the request (remote support Session Timers but
932 * doesn't want to use it, it just specify Min-SE) and local SE is
933 * lower than Min-SE specified by remote.
934 */
935 inv->timer->setting.sess_expires = min_se;
936 }
937
938 /* Set the refresher */
939 if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
940 inv->timer->refresher = TR_UAC;
941 else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
942 inv->timer->refresher = TR_UAS;
Nanang Izzuddinfc9efff2009-08-12 19:04:02 +0000943 else {
944 /* If refresher role (i.e: ours or peer) has been set/negotiated,
945 * better to keep it.
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000946 */
Nanang Izzuddinfc9efff2009-08-12 19:04:02 +0000947 if (inv->timer->refresher != TR_UNKNOWN) {
948 pj_bool_t as_refresher;
949
950 /* Check our refresher role */
951 as_refresher =
952 (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
953 (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
954
955 /* Update refresher role */
956 inv->timer->refresher = as_refresher? TR_UAS : TR_UAC;
957 } else {
958 /* If UAC support timer (currently check the existance of
959 * Session-Expires header in the request), set UAC as refresher.
960 */
961 inv->timer->refresher = se_hdr? TR_UAC : TR_UAS;
962 }
963 }
964
965 /* Remember our role in this transaction */
966 inv->timer->role = PJSIP_ROLE_UAS;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000967
968 /* Set active flag */
969 inv->timer->active = PJ_TRUE;
970
971 return PJ_SUCCESS;
972}
973
974/*
975 * Handle outgoing response with status code 2xx & 422.
976 */
977PJ_DEF(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv,
978 pjsip_tx_data *tdata)
979{
980 pjsip_msg *msg;
981
982 /* Check if Session Timers is supported */
983 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
984 return PJ_SUCCESS;
985
986 pj_assert(is_initialized);
987 PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
988
989 msg = tdata->msg;
990
991 if (msg->line.status.code/100 == 2)
992 {
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000993 if (inv->timer && inv->timer->active) {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000994 /* Add Session-Expires header and start the timer */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000995 add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE);
996 start_timer(inv);
997 }
998 }
999 else if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
1000 {
1001 /* Add Min-SE header */
1002 add_timer_headers(inv, tdata, PJ_FALSE, PJ_TRUE);
1003 }
1004
1005 return PJ_SUCCESS;
1006}
1007
1008
1009/*
1010 * End the Session Timers.
1011 */
1012PJ_DEF(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv)
1013{
1014 PJ_ASSERT_RETURN(inv, PJ_EINVAL);
1015
1016 if (inv->timer) {
1017 /* Reset active flag */
1018 inv->timer->active = PJ_FALSE;
1019
1020 /* Stop Session Timers */
1021 stop_timer(inv);
1022 }
1023
1024 return PJ_SUCCESS;
1025}