blob: 1200d133a010e9d4a29e43ad2e9cdff50638bb53 [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 */
52typedef struct pjsip_timer
53{
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
65} pjsip_timer;
66
67/* External global vars */
68extern pj_bool_t pjsip_use_compact_form;
69extern const pjsip_method pjsip_update_method;
70
71/* Local functions & vars */
72static void stop_timer(pjsip_inv_session *inv);
73static void start_timer(pjsip_inv_session *inv);
74static pj_bool_t is_initialized;
75
76/*
77 * Session-Expires header vptr.
78 */
79static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
80 char *buf, pj_size_t size);
81static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
82 const pjsip_sess_expires_hdr *hdr);
83static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
84 pj_pool_t *pool,
85 const pjsip_sess_expires_hdr* hdr);
86
87static pjsip_hdr_vptr se_hdr_vptr =
88{
89 (pjsip_hdr_clone_fptr) &se_hdr_clone,
90 (pjsip_hdr_clone_fptr) &se_hdr_shallow_clone,
91 (pjsip_hdr_print_fptr) &se_hdr_print,
92};
93
94/*
95 * Min-SE header vptr.
96 */
97static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
98 char *buf, pj_size_t size);
99static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
100 const pjsip_min_se_hdr *hdr);
101static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
102 pj_pool_t *pool,
103 const pjsip_min_se_hdr* hdr);
104
105static pjsip_hdr_vptr min_se_hdr_vptr =
106{
107 (pjsip_hdr_clone_fptr) &min_se_hdr_clone,
108 (pjsip_hdr_clone_fptr) &min_se_hdr_shallow_clone,
109 (pjsip_hdr_print_fptr) &min_se_hdr_print,
110};
111
112/*
113 * Session-Expires header vptr.
114 */
115static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
116 char *buf, pj_size_t size)
117{
118 char *p = buf;
119 char *endbuf = buf+size;
120 int printed;
121 const pjsip_parser_const_t *pc = pjsip_parser_const();
122 const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
123
Nanang Izzuddin65add622009-08-11 16:26:20 +0000124 /* Print header name and value */
125 if ((endbuf - p) < (hname->slen + 16))
126 return -1;
127
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000128 copy_advance(p, (*hname));
129 *p++ = ':';
130 *p++ = ' ';
131
132 printed = pj_utoa(hdr->sess_expires, p);
133 p += printed;
134
Nanang Izzuddin65add622009-08-11 16:26:20 +0000135 /* Print 'refresher' param */
136 if (hdr->refresher.slen)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000137 {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000138 if ((endbuf - p) < (STR_REFRESHER.slen + 2 + hdr->refresher.slen))
139 return -1;
140
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000141 *p++ = ';';
142 copy_advance(p, STR_REFRESHER);
143 *p++ = '=';
144 copy_advance(p, hdr->refresher);
145 }
146
Nanang Izzuddin65add622009-08-11 16:26:20 +0000147 /* Print generic params */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000148 printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
149 &pc->pjsip_TOKEN_SPEC,
150 &pc->pjsip_TOKEN_SPEC, ';');
151 if (printed < 0)
152 return printed;
153
154 p += printed;
155 return p - buf;
156}
157
158static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
159 const pjsip_sess_expires_hdr *hsrc)
160{
161 pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(pool);
162 hdr->sess_expires = hsrc->sess_expires;
163 pj_strdup(pool, &hdr->refresher, &hsrc->refresher);
164 pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
165 return hdr;
166}
167
168static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
169 pj_pool_t *pool,
170 const pjsip_sess_expires_hdr* hsrc)
171{
172 pjsip_sess_expires_hdr *hdr = PJ_POOL_ALLOC_T(pool,pjsip_sess_expires_hdr);
173 pj_memcpy(hdr, hsrc, sizeof(*hdr));
174 pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
175 return hdr;
176}
177
178/*
179 * Min-SE header vptr.
180 */
181static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
182 char *buf, pj_size_t size)
183{
184 char *p = buf;
185 char *endbuf = buf+size;
186 int printed;
187 const pjsip_parser_const_t *pc = pjsip_parser_const();
188
Nanang Izzuddin65add622009-08-11 16:26:20 +0000189 /* Print header name and value */
190 if ((endbuf - p) < (hdr->name.slen + 16))
191 return -1;
192
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000193 copy_advance(p, hdr->name);
194 *p++ = ':';
195 *p++ = ' ';
196
197 printed = pj_utoa(hdr->min_se, p);
198 p += printed;
199
Nanang Izzuddin65add622009-08-11 16:26:20 +0000200 /* Print generic params */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000201 printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
202 &pc->pjsip_TOKEN_SPEC,
203 &pc->pjsip_TOKEN_SPEC, ';');
204 if (printed < 0)
205 return printed;
206
207 p += printed;
208 return p - buf;
209}
210
211static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
212 const pjsip_min_se_hdr *hsrc)
213{
214 pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(pool);
215 hdr->min_se = hsrc->min_se;
216 pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
217 return hdr;
218}
219
220static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
221 pj_pool_t *pool,
222 const pjsip_min_se_hdr* hsrc)
223{
224 pjsip_min_se_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_min_se_hdr);
225 pj_memcpy(hdr, hsrc, sizeof(*hdr));
226 pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
227 return hdr;
228}
229
230
231/*
232 * Parse Session-Expires header.
233 */
234static pjsip_hdr *parse_hdr_se(pjsip_parse_ctx *ctx)
235{
236 pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(ctx->pool);
237 const pjsip_parser_const_t *pc = pjsip_parser_const();
238 pj_str_t token;
239
240 pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
241 hdr->sess_expires = pj_strtoul(&token);
242
243 while (*ctx->scanner->curptr == ';') {
244 pj_str_t pname, pvalue;
245
246 pj_scan_get_char(ctx->scanner);
247 pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
248
249 if (pj_stricmp(&pname, &STR_REFRESHER)==0) {
250 hdr->refresher = pvalue;
251 } else {
252 pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
253 param->name = pname;
254 param->value = pvalue;
255 pj_list_push_back(&hdr->other_param, param);
256 }
257 }
258 pjsip_parse_end_hdr_imp( ctx->scanner );
259 return (pjsip_hdr*)hdr;
260}
261
262/*
263 * Parse Min-SE header.
264 */
265static pjsip_hdr *parse_hdr_min_se(pjsip_parse_ctx *ctx)
266{
267 pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(ctx->pool);
268 const pjsip_parser_const_t *pc = pjsip_parser_const();
269 pj_str_t token;
270
271 pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
272 hdr->min_se = pj_strtoul(&token);
273
274 while (*ctx->scanner->curptr == ';') {
275 pj_str_t pname, pvalue;
276 pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
277
278 pj_scan_get_char(ctx->scanner);
279 pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
280
281 param->name = pname;
282 param->value = pvalue;
283 pj_list_push_back(&hdr->other_param, param);
284 }
285 pjsip_parse_end_hdr_imp( ctx->scanner );
286 return (pjsip_hdr*)hdr;
287}
288
289
290/* Add "Session-Expires" and "Min-SE" headers. Note that "Min-SE" header
291 * can only be added to INVITE/UPDATE request and 422 response.
292 */
293static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata,
294 pj_bool_t add_se, pj_bool_t add_min_se)
295{
296 pjsip_timer *timer = inv->timer;
297
298 /* Add Session-Expires header */
299 if (add_se) {
300 pjsip_sess_expires_hdr *hdr;
301
302 hdr = pjsip_sess_expires_hdr_create(tdata->pool);
303 hdr->sess_expires = timer->setting.sess_expires;
304 if (timer->refresher != TR_UNKNOWN)
305 hdr->refresher = (timer->refresher == TR_UAC? STR_UAC : STR_UAS);
306
307 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
308 }
309
310 /* Add Min-SE header */
311 if (add_min_se) {
312 pjsip_min_se_hdr *hdr;
313
314 hdr = pjsip_min_se_hdr_create(tdata->pool);
315 hdr->min_se = timer->setting.min_se;
316
317 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
318 }
319}
320
321/* Timer callback. When the timer is fired, it can be time to refresh
322 * the session if UA is the refresher, otherwise it is time to end
323 * the session.
324 */
325void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
326{
327 pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data;
328 pjsip_tx_data *tdata = NULL;
329 pj_status_t status;
330 pj_bool_t as_refresher;
331
332 pj_assert(inv);
333
334 PJ_UNUSED_ARG(timer_heap);
335
336 /* Lock dialog. */
337 pjsip_dlg_inc_lock(inv->dlg);
338
339 /* Check our role */
340 as_refresher =
Nanang Izzuddin65add622009-08-11 16:26:20 +0000341 (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
342 (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS);
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000343
344 /* Do action based on role, refresher or refreshee */
345 if (as_refresher) {
346
347 pj_time_val now;
348
349 /* Refresher, refresh the session */
350 if (inv->timer->use_update) {
351 /* Create UPDATE request without offer */
352 status = pjsip_inv_update(inv, NULL, NULL, &tdata);
353 } else {
354 /* Create re-INVITE without modifying session */
355 pjsip_msg_body *body;
356 const pjmedia_sdp_session *offer = NULL;
357
358 pj_assert(pjmedia_sdp_neg_get_state(inv->neg) ==
359 PJMEDIA_SDP_NEG_STATE_DONE);
360
361 status = pjsip_inv_invite(inv, &tdata);
362 if (status == PJ_SUCCESS)
363 status = pjmedia_sdp_neg_send_local_offer(inv->pool,
364 inv->neg, &offer);
365 if (status == PJ_SUCCESS)
366 status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
367 if (status == PJ_SUCCESS) {
368 status = pjsip_create_sdp_body(tdata->pool,
369 (pjmedia_sdp_session*)offer, &body);
370 tdata->msg->body = body;
371 }
372 }
373
374 pj_gettimeofday(&now);
375 PJ_LOG(4, (inv->pool->obj_name,
376 "Refresh session after %ds (expiration period=%ds)",
377 (now.sec-inv->timer->last_refresh.sec),
378 inv->timer->setting.sess_expires));
379 } else {
380
381 pj_time_val now;
382
383 /* Refreshee, terminate the session */
384 status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT,
385 NULL, &tdata);
386
387 pj_gettimeofday(&now);
388 PJ_LOG(3, (inv->pool->obj_name,
389 "No session refresh received after %ds "
390 "(expiration period=%ds), stopping session now!",
391 (now.sec-inv->timer->last_refresh.sec),
392 inv->timer->setting.sess_expires));
393 }
394
395 /* Unlock dialog. */
396 pjsip_dlg_dec_lock(inv->dlg);
397
398 /* Send message, if any */
399 if (tdata && status == PJ_SUCCESS) {
400 status = pjsip_inv_send_msg(inv, tdata);
401 }
402
403 /* Print error message, if any */
404 if (status != PJ_SUCCESS) {
405 char errmsg[PJ_ERR_MSG_SIZE];
406
407 if (tdata)
408 pjsip_tx_data_dec_ref(tdata);
409
410 pj_strerror(status, errmsg, sizeof(errmsg));
411 PJ_LOG(2, (inv->pool->obj_name, "Session timer fails in %s session, "
412 "err code=%d (%s)",
413 (as_refresher? "refreshing" :
414 "terminating"),
415 status, errmsg));
416 }
417}
418
419/* Start Session Timers */
420static void start_timer(pjsip_inv_session *inv)
421{
422 pjsip_timer *timer = inv->timer;
423 pj_time_val delay = {0};
424
425 pj_assert(inv->timer->active == PJ_TRUE);
426
427 stop_timer(inv);
428
429 pj_timer_entry_init(&timer->timer,
430 1, /* id */
431 inv, /* user data */
432 timer_cb); /* callback */
433
434 /* Set delay based on role, refresher or refreshee */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000435 if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
436 (timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000437 {
438 /* Next refresh, the delay is half of session expire */
439 delay.sec = timer->setting.sess_expires / 2;
440 } else {
441 /* Send BYE if no refresh received until this timer fired, delay
442 * is the minimum of 32 seconds and one third of the session interval
443 * before session expiration.
444 */
445 delay.sec = timer->setting.sess_expires -
446 timer->setting.sess_expires/3;
447 delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
448 }
449
450 /* Schedule the timer */
451 pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);
452
453 /* Update last refresh time */
454 pj_gettimeofday(&timer->last_refresh);
455}
456
457/* Stop Session Timers */
458static void stop_timer(pjsip_inv_session *inv)
459{
460 if (inv->timer->timer.id != 0) {
461 pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->timer);
462 inv->timer->timer.id = 0;
463 }
464}
465
466/*
467 * Initialize Session Timers support in PJSIP.
468 */
469PJ_DEF(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt)
470{
471 pj_status_t status;
472
473 PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
474
475 if (is_initialized)
476 return PJ_SUCCESS;
477
478 /* Register Session-Expires header parser */
479 status = pjsip_register_hdr_parser( STR_SE.ptr, STR_SHORT_SE.ptr,
480 &parse_hdr_se);
481 if (status != PJ_SUCCESS)
482 return status;
483
484 /* Register Min-SE header parser */
485 status = pjsip_register_hdr_parser( STR_MIN_SE.ptr, NULL,
486 &parse_hdr_min_se);
487 if (status != PJ_SUCCESS)
488 return status;
489
490 /* Register 'timer' capability to endpoint */
491 status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED,
492 NULL, 1, &STR_TIMER);
493 if (status != PJ_SUCCESS)
494 return status;
495
496 is_initialized = PJ_TRUE;
497
498 return PJ_SUCCESS;
499}
500
501
502/*
503 * Initialize Session Timers setting with default values.
504 */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000505PJ_DEF(pj_status_t) pjsip_timer_setting_default(pjsip_timer_setting *setting)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000506{
507 pj_bzero(setting, sizeof(pjsip_timer_setting));
508
Nanang Izzuddin65add622009-08-11 16:26:20 +0000509 setting->sess_expires = PJSIP_SESS_TIMER_DEF_SE;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000510 setting->min_se = ABS_MIN_SE;
511
512 return PJ_SUCCESS;
513}
514
515/*
516 * Initialize Session Timers in an INVITE session.
517 */
518PJ_DEF(pj_status_t) pjsip_timer_init_session(
519 pjsip_inv_session *inv,
520 const pjsip_timer_setting *setting)
521{
522 pjsip_timer_setting *s;
523
524 pj_assert(is_initialized);
525 PJ_ASSERT_RETURN(inv, PJ_EINVAL);
526
527 /* Allocate and/or reset Session Timers structure */
528 if (!inv->timer)
529 inv->timer = PJ_POOL_ZALLOC_T(inv->pool, pjsip_timer);
530 else
531 pj_bzero(inv->timer, sizeof(pjsip_timer));
532
533 s = &inv->timer->setting;
534
535 /* Init Session Timers setting */
536 if (setting) {
537 PJ_ASSERT_RETURN(setting->min_se >= ABS_MIN_SE,
538 PJ_ETOOSMALL);
539 PJ_ASSERT_RETURN(setting->sess_expires >= setting->min_se,
540 PJ_EINVAL);
541
542 pj_memcpy(s, setting, sizeof(*s));
543 } else {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000544 pjsip_timer_setting_default(s);
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000545 }
546
547 return PJ_SUCCESS;
548}
549
550
551/*
552 * Create Session-Expires header.
553 */
554PJ_DEF(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create(
555 pj_pool_t *pool)
556{
557 pjsip_sess_expires_hdr *hdr = PJ_POOL_ZALLOC_T(pool,
558 pjsip_sess_expires_hdr);
559
560 pj_assert(is_initialized);
561
562 hdr->type = PJSIP_H_OTHER;
563 hdr->name = STR_SE;
564 hdr->sname = STR_SHORT_SE;
565 hdr->vptr = &se_hdr_vptr;
566 pj_list_init(hdr);
567 pj_list_init(&hdr->other_param);
568 return hdr;
569}
570
571
572/*
573 * Create Min-SE header.
574 */
575PJ_DEF(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool)
576{
577 pjsip_min_se_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_min_se_hdr);
578
579 pj_assert(is_initialized);
580
581 hdr->type = PJSIP_H_OTHER;
582 hdr->name = STR_MIN_SE;
583 hdr->vptr = &min_se_hdr_vptr;
584 pj_list_init(hdr);
585 pj_list_init(&hdr->other_param);
586 return hdr;
587}
588
589
590/*
591 * This function generates headers for Session Timers for intial and
592 * refresh INVITE or UPDATE.
593 */
594PJ_DEF(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv,
595 pjsip_tx_data *tdata)
596{
597 PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
598
599 /* Check if Session Timers is supported */
600 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
601 return PJ_SUCCESS;
602
603 pj_assert(is_initialized);
604
605 /* Make sure Session Timers is initialized */
606 if (inv->timer == NULL)
607 pjsip_timer_init_session(inv, NULL);
608
609 /* Add Session Timers headers */
610 add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
611
612 return PJ_SUCCESS;
613}
614
615/*
616 * This function will handle Session Timers part of INVITE/UPDATE
617 * responses with code:
618 * - 422 (Session Interval Too Small)
619 * - 2xx final response
620 */
621PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv,
Nanang Izzuddin65add622009-08-11 16:26:20 +0000622 const pjsip_rx_data *rdata,
623 pjsip_status_code *st_code)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000624{
625 const pjsip_msg *msg;
626
Nanang Izzuddin65add622009-08-11 16:26:20 +0000627 PJ_ASSERT_ON_FAIL(inv && rdata,
628 {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000629
630 /* Check if Session Timers is supported */
631 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
632 return PJ_SUCCESS;
633
634 pj_assert(is_initialized);
635
636 msg = rdata->msg_info.msg;
637 pj_assert(msg->type == PJSIP_RESPONSE_MSG);
638
639 /* Only process response of INVITE or UPDATE */
640 if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD &&
641 pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
642 {
643 return PJ_SUCCESS;
644 }
645
646 if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) {
647 /* Our Session-Expires is too small, let's update it based on
648 * Min-SE header in the response.
649 */
650 pjsip_tx_data *tdata;
651 pjsip_min_se_hdr *min_se_hdr;
652 pjsip_hdr *hdr;
653 pjsip_via_hdr *via;
654
655 /* Get Min-SE value from response */
656 min_se_hdr = (pjsip_min_se_hdr*)
657 pjsip_msg_find_hdr_by_name(msg, &STR_MIN_SE, NULL);
658 if (min_se_hdr == NULL) {
659 /* Response 422 should contain Min-SE header */
660 return PJ_SUCCESS;
661 }
662
663 /* Session Timers should have been initialized here */
664 pj_assert(inv->timer);
665
666 /* Update Min-SE */
667 inv->timer->setting.min_se = PJ_MAX(min_se_hdr->min_se,
668 inv->timer->setting.min_se);
669
670 /* Update Session Timers setting */
671 if (inv->timer->setting.sess_expires < inv->timer->setting.min_se)
672 inv->timer->setting.sess_expires = inv->timer->setting.min_se;
673
674 /* Prepare to restart the request */
675
676 /* Get the original INVITE request. */
677 tdata = inv->invite_req;
678
679 /* Remove branch param in Via header. */
680 via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
681 pj_assert(via);
682 via->branch_param.slen = 0;
683
684 /* Restore strict route set.
685 * See http://trac.pjsip.org/repos/ticket/492
686 */
687 pjsip_restore_strict_route_set(tdata);
688
689 /* Must invalidate the message! */
690 pjsip_tx_data_invalidate_msg(tdata);
691
692 pjsip_tx_data_add_ref(tdata);
693
694 /* Update Session Timers headers */
695 hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg,
696 &STR_MIN_SE, NULL);
697 if (hdr != NULL) pj_list_erase(hdr);
698
699 hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_names(tdata->msg, &STR_SE,
700 &STR_SHORT_SE, NULL);
701 if (hdr != NULL) pj_list_erase(hdr);
702
703 add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
704
705 /* Restart UAC */
706 pjsip_inv_uac_restart(inv, PJ_FALSE);
707 pjsip_inv_send_msg(inv, tdata);
708
709 return PJ_SUCCESS;
710
711 } else if (msg->line.status.code/100 == 2) {
712
713 pjsip_sess_expires_hdr *se_hdr;
714
715 /* Find Session-Expires header */
716 se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
717 msg, &STR_SE,
718 &STR_SHORT_SE, NULL);
719 if (se_hdr == NULL) {
720 /* Remote doesn't support/want Session Timers, check if local
721 * require or force to use Session Timers.
722 */
723 if (inv->options & PJSIP_INV_REQUIRE_TIMER) {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000724 if (st_code)
725 *st_code = PJSIP_SC_EXTENSION_REQUIRED;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000726 pjsip_timer_end_session(inv);
727 return PJSIP_ERRNO_FROM_SIP_STATUS(
728 PJSIP_SC_EXTENSION_REQUIRED);
729 }
730
731 if ((inv->options & PJSIP_INV_ALWAYS_USE_TIMER) == 0) {
732 /* Session Timers not forced */
733 pjsip_timer_end_session(inv);
734 return PJ_SUCCESS;
735 }
736 }
737
738 /* Make sure Session Timers is initialized */
739 if (inv->timer == NULL)
740 pjsip_timer_init_session(inv, NULL);
741
742 /* Session expiration period specified by remote is lower than our
743 * Min-SE.
744 */
745 if (se_hdr &&
746 se_hdr->sess_expires < inv->timer->setting.min_se)
747 {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000748 if (st_code)
749 *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000750 pjsip_timer_end_session(inv);
751 return PJSIP_ERRNO_FROM_SIP_STATUS(
752 PJSIP_SC_SESSION_TIMER_TOO_SMALL);
753 }
754
755 /* Update SE. Session-Expires in response cannot be lower than Min-SE.
756 * Session-Expires in response can only be equal or lower than in
757 * request.
758 */
759 if (se_hdr &&
760 se_hdr->sess_expires <= inv->timer->setting.sess_expires &&
761 se_hdr->sess_expires >= inv->timer->setting.min_se)
762 {
763 /* Good SE from remote, update local SE */
764 inv->timer->setting.sess_expires = se_hdr->sess_expires;
765 }
766
767 /* Set the refresher */
768 if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
769 inv->timer->refresher = TR_UAC;
770 else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
771 inv->timer->refresher = TR_UAS;
772 else
773 /* UAS should set the refresher, however, there is a case that
774 * UAS doesn't support/want Session Timers but the UAC insists
775 * to use Session Timers.
776 */
777 inv->timer->refresher = TR_UAC;
778
779 PJ_TODO(CHECK_IF_REMOTE_SUPPORT_UPDATE);
780
Nanang Izzuddin65add622009-08-11 16:26:20 +0000781 /* Remember our role in this transaction */
782 inv->timer->role = PJSIP_ROLE_UAC;
783
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000784 /* Finally, set active flag and start the Session Timers */
785 inv->timer->active = PJ_TRUE;
786 start_timer(inv);
787 }
788
789 return PJ_SUCCESS;
790}
791
792/*
793 * Handle incoming INVITE or UPDATE request.
794 */
795PJ_DEF(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv,
Nanang Izzuddin65add622009-08-11 16:26:20 +0000796 const pjsip_rx_data *rdata,
797 pjsip_status_code *st_code)
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000798{
799 pjsip_min_se_hdr *min_se_hdr;
800 pjsip_sess_expires_hdr *se_hdr;
801 const pjsip_msg *msg;
802 unsigned min_se;
803
Nanang Izzuddin65add622009-08-11 16:26:20 +0000804 PJ_ASSERT_ON_FAIL(inv && rdata,
805 {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000806
807 /* Check if Session Timers is supported */
808 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
809 return PJ_SUCCESS;
810
811 pj_assert(is_initialized);
812
813 msg = rdata->msg_info.msg;
814 pj_assert(msg->type == PJSIP_REQUEST_MSG);
815
816 /* Only process INVITE or UPDATE request */
817 if (msg->line.req.method.id != PJSIP_INVITE_METHOD &&
818 pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
819 {
820 return PJ_SUCCESS;
821 }
822
823 /* Find Session-Expires header */
824 se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
825 msg, &STR_SE, &STR_SHORT_SE, NULL);
826 if (se_hdr == NULL) {
827 /* Remote doesn't support/want Session Timers, check if local
828 * require or force to use Session Timers. Note that Supported and
829 * Require headers negotiation should have been verified by invite
830 * session.
831 */
832 if ((inv->options &
833 (PJSIP_INV_REQUIRE_TIMER | PJSIP_INV_ALWAYS_USE_TIMER)) == 0)
834 {
835 /* Session Timers not forced/required */
836 pjsip_timer_end_session(inv);
837 return PJ_SUCCESS;
838 }
839 }
840
841 /* Make sure Session Timers is initialized */
842 if (inv->timer == NULL)
843 pjsip_timer_init_session(inv, NULL);
844
845 /* Find Min-SE header */
846 min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(msg,
847 &STR_MIN_SE, NULL);
848 /* Update Min-SE */
849 min_se = inv->timer->setting.min_se;
850 if (min_se_hdr)
851 min_se = PJ_MAX(min_se_hdr->min_se, min_se);
852
853 /* Validate SE. Session-Expires cannot be lower than Min-SE
854 * (or 90 seconds if Min-SE is not set).
855 */
Nanang Izzuddin65add622009-08-11 16:26:20 +0000856 if (se_hdr && se_hdr->sess_expires < min_se) {
857 if (st_code)
858 *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000859 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_SESSION_TIMER_TOO_SMALL);
Nanang Izzuddin65add622009-08-11 16:26:20 +0000860 }
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000861
862 /* Update SE. Note that there is a case that SE is not available in the
863 * request (which means remote doesn't want/support it), but local insists
864 * to use Session Timers.
865 */
866 if (se_hdr) {
867 /* Update SE as specified by peer. */
868 inv->timer->setting.sess_expires = se_hdr->sess_expires;
869 } else if (inv->timer->setting.sess_expires < min_se) {
870 /* There is no SE in the request (remote support Session Timers but
871 * doesn't want to use it, it just specify Min-SE) and local SE is
872 * lower than Min-SE specified by remote.
873 */
874 inv->timer->setting.sess_expires = min_se;
875 }
876
877 /* Set the refresher */
878 if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
879 inv->timer->refresher = TR_UAC;
880 else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
881 inv->timer->refresher = TR_UAS;
882 else
883 /* If UAC support timer (currently check the existance of
884 * Session-Expires header in the request), set UAC as refresher.
885 */
886 inv->timer->refresher = se_hdr? TR_UAC : TR_UAS;
887
888 /* Set active flag */
889 inv->timer->active = PJ_TRUE;
890
891 return PJ_SUCCESS;
892}
893
894/*
895 * Handle outgoing response with status code 2xx & 422.
896 */
897PJ_DEF(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv,
898 pjsip_tx_data *tdata)
899{
900 pjsip_msg *msg;
901
902 /* Check if Session Timers is supported */
903 if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
904 return PJ_SUCCESS;
905
906 pj_assert(is_initialized);
907 PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
908
909 msg = tdata->msg;
910
911 if (msg->line.status.code/100 == 2)
912 {
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000913 if (inv->timer && inv->timer->active) {
Nanang Izzuddin65add622009-08-11 16:26:20 +0000914 /* Remember our role in this transaction */
915 inv->timer->role = PJSIP_ROLE_UAS;
916
917 /* Add Session-Expires header and start the timer */
Nanang Izzuddin59dffb12009-08-11 12:42:38 +0000918 add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE);
919 start_timer(inv);
920 }
921 }
922 else if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
923 {
924 /* Add Min-SE header */
925 add_timer_headers(inv, tdata, PJ_FALSE, PJ_TRUE);
926 }
927
928 return PJ_SUCCESS;
929}
930
931
932/*
933 * End the Session Timers.
934 */
935PJ_DEF(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv)
936{
937 PJ_ASSERT_RETURN(inv, PJ_EINVAL);
938
939 if (inv->timer) {
940 /* Reset active flag */
941 inv->timer->active = PJ_FALSE;
942
943 /* Stop Session Timers */
944 stop_timer(inv);
945 }
946
947 return PJ_SUCCESS;
948}
949