1 /*
2 * This file is part of the Sofia-SIP package
3 *
4 * Copyright (C) 2006 Nokia Corporation.
5 *
6 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 /**@CFILE nua_dialog.c
26 * @brief Dialog and dialog usage handling
27 *
28 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
29 *
30 * @date Created: Wed Mar 8 11:48:49 EET 2006 ppessi
31 */
32
33 #include "config.h"
34
35 #include <stddef.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <limits.h>
39
40 #include <assert.h>
41
42 #include <sofia-sip/su_string.h>
43 #include <sofia-sip/su_uniqueid.h>
44
45 #include <sofia-sip/sip_protos.h>
46 #include <sofia-sip/sip_status.h>
47
48 #define NUA_OWNER_T su_home_t
49
50 #include "nua_dialog.h"
51
52 #define SU_LOG (nua_log)
53 #include <sofia-sip/su_debug.h>
54
55 #ifndef NONE
56
57 #ifndef _MSC_VER
58 #define NONE ((void *)-1)
59 #else
60 #define NONE ((void *)(INT_PTR)-1)
61 #endif
62 #endif
63
64 /* ======================================================================== */
65 /* Dialog handling */
66
67 static void nua_dialog_usage_remove_at(nua_owner_t*, nua_dialog_state_t*,
68 nua_dialog_usage_t**,
69 nua_client_request_t *cr,
70 nua_server_request_t *sr);
71 static void nua_dialog_log_usage(nua_owner_t *, nua_dialog_state_t *);
72
73 /**@internal
74 * UAS tag and route.
75 *
76 * Update dialog tags and route on the UAS side.
77 *
78 * @param own dialog owner
79 * @param ds dialog state
80 * @param sip SIP message containing response used to update dialog
81 * @param rtag if true, set remote tag within the leg
82 */
nua_dialog_uas_route(nua_owner_t * own,nua_dialog_state_t * ds,sip_t const * sip,int rtag)83 void nua_dialog_uas_route(nua_owner_t *own,
84 nua_dialog_state_t *ds,
85 sip_t const *sip,
86 int rtag)
87 {
88 int established = nua_dialog_is_established(ds);
89
90 if (!established && sip->sip_from->a_tag)
91 ds->ds_remote_tag = su_strdup(own, sip->sip_from->a_tag);
92
93 if (ds->ds_leg == NULL)
94 return;
95
96 nta_leg_server_route(ds->ds_leg, sip->sip_record_route, sip->sip_contact);
97 ds->ds_route = ds->ds_route || sip->sip_record_route || sip->sip_contact;
98
99 if (rtag && !established && sip->sip_from->a_tag)
100 nta_leg_rtag(ds->ds_leg, sip->sip_from->a_tag);
101 }
102
103 /**@internal
104 * UAC tag and route.
105 *
106 * Update dialog tags and route on the UAC side.
107 *
108 * @param own dialog owner
109 * @param ds dialog state
110 * @param sip SIP message containing response used to update dialog
111 * @param rtag if true, set remote tag within the leg
112 * @param initial if true, @a sip is response to initial transaction
113 */
nua_dialog_uac_route(nua_owner_t * own,nua_dialog_state_t * ds,sip_t const * sip,int rtag,int initial)114 void nua_dialog_uac_route(nua_owner_t *own,
115 nua_dialog_state_t *ds,
116 sip_t const *sip,
117 int rtag,
118 int initial)
119 {
120 int established = nua_dialog_is_established(ds);
121 int status = sip->sip_status->st_status;
122
123 if (!established && sip->sip_to->a_tag)
124 ds->ds_remote_tag = su_strdup(own, sip->sip_to->a_tag);
125
126 if (ds->ds_leg == NULL)
127 return;
128
129 if (initial && status >= 200)
130 nta_leg_client_reroute(ds->ds_leg, sip->sip_record_route, sip->sip_contact, 1);
131 else
132 nta_leg_client_reroute(ds->ds_leg, sip->sip_record_route, sip->sip_contact, 0);
133
134 ds->ds_route = ds->ds_route || sip->sip_record_route || sip->sip_contact;
135
136 if (rtag && !established && sip->sip_to->a_tag)
137 nta_leg_rtag(ds->ds_leg, sip->sip_to->a_tag);
138 }
139
140 /**@internal Store information from remote endpoint. */
nua_dialog_store_peer_info(nua_owner_t * own,nua_dialog_state_t * ds,sip_t const * sip)141 void nua_dialog_store_peer_info(nua_owner_t *own,
142 nua_dialog_state_t *ds,
143 sip_t const *sip)
144 {
145 nua_dialog_peer_info_t *nr = ds->ds_remote_ua;
146 nua_dialog_usage_t *du;
147 nua_dialog_peer_info_t old[1];
148
149 *old = *nr;
150
151 if (sip && sip->sip_status &&
152 sip->sip_status->st_status >= 300 &&
153 sip->sip_status->st_status <= 399)
154 sip = NULL; /* Redirected */
155
156 if (sip == NULL) {
157 nr->nr_via = NULL, su_free(own, old->nr_via);
158 nr->nr_allow = NULL, su_free(own, old->nr_allow);
159 nr->nr_accept = NULL, su_free(own, old->nr_accept);
160 nr->nr_require = NULL, su_free(own, old->nr_require);
161 nr->nr_supported = NULL, su_free(own, old->nr_supported);
162 nr->nr_user_agent = NULL, su_free(own, old->nr_user_agent);
163 return;
164 }
165
166 if (sip->sip_allow) {
167 nr->nr_allow = sip_allow_dup(own, sip->sip_allow);
168 su_free(own, old->nr_allow);
169 }
170
171 if (sip->sip_accept) {
172 nr->nr_accept = sip_accept_dup(own, sip->sip_accept);
173 su_free(own, old->nr_accept);
174 }
175
176 if (sip->sip_require) {
177 nr->nr_require = sip_require_dup(own, sip->sip_require);
178 su_free(own, old->nr_require);
179 }
180
181 if (sip->sip_supported) {
182 nr->nr_supported = sip_supported_dup(own, sip->sip_supported);
183 su_free(own, old->nr_supported);
184 }
185
186 if (sip->sip_via) {
187 nr->nr_via = sip_via_dup(own, sip->sip_via);
188 su_free(own, old->nr_via);
189 }
190
191 if (sip->sip_user_agent) {
192 nr->nr_user_agent = sip_user_agent_dup(own, sip->sip_user_agent);
193 su_free(own, old->nr_user_agent);
194 }
195 else if (sip->sip_server) {
196 nr->nr_user_agent = sip_user_agent_dup(own, sip->sip_server);
197 su_free(own, old->nr_user_agent);
198 }
199
200 for (du = ds->ds_usage; du; du = du->du_next) {
201 if (du->du_class->usage_peer_info)
202 du->du_class->usage_peer_info(du, ds, sip);
203 }
204 }
205
206 /** Remove dialog information. */
nua_dialog_zap(nua_owner_t * own,nua_dialog_state_t * ds)207 int nua_dialog_zap(nua_owner_t *own,
208 nua_dialog_state_t *ds)
209 {
210 /* zap peer info */
211 nua_dialog_store_peer_info(own, ds, NULL);
212 /* Local Contact */
213 msg_header_free(own, (msg_header_t *)ds->ds_ltarget), ds->ds_ltarget = NULL;
214 /* Leg */
215 nta_leg_destroy(ds->ds_leg), ds->ds_leg = NULL;
216 /* Remote tag */
217 su_free(own, (void *)ds->ds_remote_tag), ds->ds_remote_tag = NULL;
218 /* Ready to set route/remote target */
219 ds->ds_route = 0;
220
221 return 0;
222 }
223
224 /** Remove dialog (if there is no other usages). */
nua_dialog_remove(nua_owner_t * own,nua_dialog_state_t * ds,nua_dialog_usage_t * usage)225 int nua_dialog_remove(nua_owner_t *own,
226 nua_dialog_state_t *ds,
227 nua_dialog_usage_t *usage)
228 {
229 if (ds->ds_usage == usage && (usage == NULL || usage->du_next == NULL)) {
230 return nua_dialog_zap(own, ds);
231 }
232 return 0;
233 }
234
235 /** @internal Get dialog usage slot. */
236 nua_dialog_usage_t **
nua_dialog_usage_at(nua_dialog_state_t const * ds,nua_usage_class const * kind,sip_event_t const * event)237 nua_dialog_usage_at(nua_dialog_state_t const *ds,
238 nua_usage_class const *kind,
239 sip_event_t const *event)
240 {
241 static nua_dialog_usage_t *none = NULL;
242
243 if (ds) {
244 nua_dialog_usage_t *du, * const * prev;
245 sip_event_t const *o;
246
247 for (prev = &ds->ds_usage; (du = *prev); prev = &du->du_next) {
248 if (du->du_class != kind)
249 continue;
250
251 if (event == NONE)
252 return (nua_dialog_usage_t **)prev;
253
254 o = du->du_event;
255
256 if (!event && !o)
257 return (nua_dialog_usage_t **)prev;
258
259 if (event != o) {
260 if (event == NULL || o == NULL)
261 continue;
262 if (!su_strmatch(event->o_type, o->o_type))
263 continue;
264 if (!su_casematch(event->o_id, o->o_id)) {
265 if (event->o_id || !su_strmatch(event->o_type, "refer"))
266 continue;
267 }
268 }
269
270 return (nua_dialog_usage_t **)prev;
271 }
272 }
273
274 return &none;
275 }
276
277 /** @internal Get a dialog usage */
nua_dialog_usage_get(nua_dialog_state_t const * ds,nua_usage_class const * kind,sip_event_t const * event)278 nua_dialog_usage_t *nua_dialog_usage_get(nua_dialog_state_t const *ds,
279 nua_usage_class const *kind,
280 sip_event_t const *event)
281 {
282 return *nua_dialog_usage_at(ds, kind, event);
283 }
284
285 /** @internal Get dialog usage name */
nua_dialog_usage_name(nua_dialog_usage_t const * du)286 char const *nua_dialog_usage_name(nua_dialog_usage_t const *du)
287 {
288 if (du == NULL)
289 return "<NULL>";
290 return du->du_class->usage_name(du);
291 }
292
293 /** @internal Add dialog usage */
nua_dialog_usage_add(nua_owner_t * own,struct nua_dialog_state * ds,nua_usage_class const * uclass,sip_event_t const * event)294 nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own,
295 struct nua_dialog_state *ds,
296 nua_usage_class const *uclass,
297 sip_event_t const *event)
298 {
299 if (ds) {
300 sip_event_t *o;
301 nua_dialog_usage_t *du, **prev_du;
302
303 prev_du = nua_dialog_usage_at(ds, uclass, event);
304 du = *prev_du;
305 if (du) { /* Already exists */
306 SU_DEBUG_5(("nua(%p): adding already existing %s usage%s%s\n",
307 (void *)own, nua_dialog_usage_name(du),
308 event ? " with event " : "", event ? event->o_type : ""));
309
310 if (prev_du != &ds->ds_usage) {
311 /* Move as a first usage in the list */
312 *prev_du = du->du_next;
313 du->du_next = ds->ds_usage;
314 ds->ds_usage = du;
315 }
316 return du;
317 }
318
319 o = event ? sip_event_dup(own, event) : NULL;
320
321 if (o != NULL || event == NULL)
322 du = su_zalloc(own, sizeof *du + uclass->usage_size);
323
324 if (du) {
325 su_home_ref(own);
326 du->du_dialog = ds;
327 du->du_class = uclass;
328 du->du_event = o;
329
330 if (uclass->usage_add(own, ds, du) < 0) {
331 su_free(own, o);
332 su_free(own, du);
333 return NULL;
334 }
335
336 SU_DEBUG_5(("nua(%p): adding %s usage%s%s\n",
337 (void *)own, nua_dialog_usage_name(du),
338 o ? " with event " : "", o ? o->o_type :""));
339
340 du->du_next = ds->ds_usage, ds->ds_usage = du;
341
342 return du;
343 }
344
345 su_free(own, o);
346 }
347
348 return NULL;
349 }
350
351 /** @internal Remove dialog usage. */
nua_dialog_usage_remove(nua_owner_t * own,nua_dialog_state_t * ds,nua_dialog_usage_t * du,nua_client_request_t * cr,nua_server_request_t * sr)352 void nua_dialog_usage_remove(nua_owner_t *own,
353 nua_dialog_state_t *ds,
354 nua_dialog_usage_t *du,
355 nua_client_request_t *cr,
356 nua_server_request_t *sr)
357 {
358 nua_dialog_usage_t **at;
359
360 assert(own); assert(ds); assert(du);
361
362 for (at = &ds->ds_usage; *at; at = &(*at)->du_next)
363 if (du == *at)
364 break;
365
366 assert(*at);
367
368 nua_dialog_usage_remove_at(own, ds, at, cr, sr);
369 }
370
371 /** @internal Remove dialog usage.
372 *
373 * Zap dialog state (leg, tag and route) if no usages remain.
374 */
375 static void
nua_dialog_usage_remove_at(nua_owner_t * own,nua_dialog_state_t * ds,nua_dialog_usage_t ** at,nua_client_request_t * cr0,nua_server_request_t * sr0)376 nua_dialog_usage_remove_at(nua_owner_t *own,
377 nua_dialog_state_t *ds,
378 nua_dialog_usage_t **at,
379 nua_client_request_t *cr0,
380 nua_server_request_t *sr0)
381 {
382 int unref = 0;
383 nua_dialog_usage_t *du = NULL;
384
385 if (*at) {
386 sip_event_t const *o = NULL;
387 nua_client_request_t *cr, *cr_next;
388 nua_server_request_t *sr, *sr_next;
389 du = *at;
390
391 *at = du->du_next;
392
393 o = du->du_event;
394
395 SU_DEBUG_5(("nua(%p): removing %s usage%s%s\n",
396 (void *)own, nua_dialog_usage_name(du),
397 o ? " with event " : "", o ? o->o_type :""));
398 du->du_class->usage_remove(own, ds, du, cr0, sr0);
399
400 /* Clean reference to saved client request */
401 if (du->du_cr)
402 nua_client_bind(du->du_cr, NULL);
403
404 /* Clean references from queued client requests */
405 for (cr = ds->ds_cr; cr; cr = cr_next) {
406 cr_next = cr->cr_next;
407 if (cr->cr_usage == du)
408 cr->cr_usage = NULL;
409 }
410
411 /* Clean references from queued server requests */
412 for (sr = ds->ds_sr; sr; sr = sr_next) {
413 sr_next = sr->sr_next;
414 if (sr->sr_usage == du) {
415 sr->sr_usage = NULL;
416 if (sr != sr0)
417 nua_server_request_destroy(sr);
418 }
419 }
420
421 unref = 1;
422 }
423
424 /* Zap dialog if there are no more usages */
425 if (ds->ds_terminating)
426 ;
427 else if (ds->ds_usage == NULL) {
428 nua_dialog_remove(own, ds, NULL);
429 ds->ds_has_events = 0;
430 if (unref) {
431 su_home_unref(own);
432 su_free(own, du);
433 }
434 return;
435 }
436 else {
437 nua_dialog_log_usage(own, ds);
438 }
439
440 if (unref) {
441 su_home_unref(own);
442 su_free(own, du);
443 }
444 }
445
446 static
nua_dialog_log_usage(nua_owner_t * own,nua_dialog_state_t * ds)447 void nua_dialog_log_usage(nua_owner_t *own, nua_dialog_state_t *ds)
448 {
449 nua_dialog_usage_t *du;
450
451 if (SU_LOG->log_level >= 3) {
452 char buffer[160];
453 size_t l = 0, N = sizeof buffer;
454 ssize_t n;
455
456 buffer[0] = '\0';
457
458 for (du = ds->ds_usage; du; du = du->du_next) {
459 msg_header_t const *h = (void *)du->du_event;
460
461 if (!h)
462 continue;
463
464 n = sip_event_e(buffer + l, N - l, h, 0);
465 if (n == -1)
466 break;
467 l += (size_t)n;
468 if (du->du_next && l + 2 < sizeof(buffer)) {
469 strcpy(buffer + l, ", ");
470 l += 2;
471 }
472 }
473
474 SU_DEBUG_3(("nua(%p): handle with %s%s%s\n", (void *)own,
475 ds->ds_has_session ? "session and " : "",
476 ds->ds_has_events ? "events " : "",
477 buffer));
478 }
479 }
480
481 /** Deinitialize dialog and its usage. @internal */
nua_dialog_deinit(nua_owner_t * own,nua_dialog_state_t * ds)482 void nua_dialog_deinit(nua_owner_t *own,
483 nua_dialog_state_t *ds)
484 {
485 ds->ds_terminating = 1;
486
487 while (ds->ds_usage) {
488 nua_dialog_usage_remove_at(own, ds, &ds->ds_usage, NULL, NULL);
489 }
490
491 nua_dialog_remove(own, ds, NULL);
492
493 ds->ds_has_events = 0;
494 ds->ds_terminating = 0;
495 }
496
nua_dialog_update_params(nua_dialog_state_t * ds,nua_handle_preferences_t const * changed,nua_handle_preferences_t const * params,nua_handle_preferences_t const * defaults)497 void nua_dialog_update_params(nua_dialog_state_t *ds,
498 nua_handle_preferences_t const *changed,
499 nua_handle_preferences_t const *params,
500 nua_handle_preferences_t const *defaults)
501 {
502 nua_dialog_usage_t *usage;
503
504 for (usage = ds->ds_usage; usage; usage = usage->du_next) {
505 usage->du_class->usage_update_params(usage, changed, params, defaults);
506 }
507 }
508
nua_base_usage_update_params(nua_dialog_usage_t const * du,nua_handle_preferences_t const * changed,nua_handle_preferences_t const * params,nua_handle_preferences_t const * defaults)509 void nua_base_usage_update_params(nua_dialog_usage_t const *du,
510 nua_handle_preferences_t const *changed,
511 nua_handle_preferences_t const *params,
512 nua_handle_preferences_t const *defaults)
513 {
514 (void)du, (void)changed, (void)params, (void)defaults;
515 }
516
517 /**@internal
518 * Set refresh value suitably.
519 *
520 * The refresh time is set either around half of the @a delta interval or,
521 * if @a delta is less than 5 minutes but longer than 90 seconds, 30..60
522 * seconds before end of interval.
523 *
524 * If @a delta is 0, the dialog usage is never refreshed.
525 */
nua_dialog_usage_set_refresh(nua_dialog_usage_t * du,unsigned delta)526 void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta)
527 {
528 if (delta == 0)
529 nua_dialog_usage_reset_refresh(du);
530 else if (delta > 90 && delta < 5 * 60)
531 /* refresh 30..60 seconds before deadline */
532 nua_dialog_usage_set_refresh_range(du, delta - 60, delta - 30);
533 else {
534 /* By default, refresh around half time before deadline */
535 unsigned min = (delta + 2) / 4;
536 unsigned max = (delta + 2) / 4 + (delta + 1) / 2;
537 if (min == 0)
538 min = 1;
539 nua_dialog_usage_set_refresh_range(du, min, max);
540 }
541 }
542
543 /**@internal Set refresh in range min..max seconds in the future. */
nua_dialog_usage_set_refresh_range(nua_dialog_usage_t * du,unsigned min,unsigned max)544 void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du,
545 unsigned min, unsigned max)
546 {
547 sip_time_t now = sip_now(), target;
548 unsigned delta;
549
550 if (max < min)
551 max = min;
552
553 if (min != max)
554 delta = su_randint(min, max);
555 else
556 delta = min;
557
558 if (now + delta >= now)
559 target = now + delta;
560 else
561 target = SIP_TIME_MAX;
562
563 SU_DEBUG_7(("nua(): refresh %s after %lu seconds (in [%u..%u])\n",
564 nua_dialog_usage_name(du), target - now, min, max));
565
566 du->du_refquested = now;
567
568 du->du_refresh = target;
569 }
570
571 /** Set absolute refresh time */
nua_dialog_usage_set_refresh_at(nua_dialog_usage_t * du,sip_time_t target)572 void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du,
573 sip_time_t target)
574 {
575 SU_DEBUG_7(("nua(): refresh %s after %lu seconds\n",
576 nua_dialog_usage_name(du), target - sip_now()));
577 du->du_refresh = target;
578 }
579
580 /**@internal Do not refresh. */
nua_dialog_usage_reset_refresh(nua_dialog_usage_t * du)581 void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du)
582 {
583 if (du) {
584 du->du_refquested = sip_now();
585 du->du_refresh = 0;
586 }
587 }
588
589 /** @internal Refresh usage. */
nua_dialog_usage_refresh(nua_owner_t * owner,nua_dialog_state_t * ds,nua_dialog_usage_t * du,sip_time_t now)590 void nua_dialog_usage_refresh(nua_owner_t *owner,
591 nua_dialog_state_t *ds,
592 nua_dialog_usage_t *du,
593 sip_time_t now)
594 {
595 assert(du && du->du_class->usage_refresh);
596 du->du_class->usage_refresh(owner, ds, du, now);
597 }
598
599 /** Terminate all dialog usages gracefully. */
nua_dialog_shutdown(nua_owner_t * owner,nua_dialog_state_t * ds)600 int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds)
601 {
602 nua_dialog_usage_t *du;
603
604 ds->ds_terminating = 1;
605
606 do {
607 for (du = ds->ds_usage; du; du = du->du_next) {
608 if (!du->du_shutdown) {
609 nua_dialog_usage_shutdown(owner, ds, du);
610 break;
611 }
612 }
613 } while (du);
614
615 return 1;
616 }
617
618 /** Shutdown (gracefully terminate) usage.
619 *
620 * @retval >0 shutdown done
621 * @retval 0 shutdown in progress
622 * @retval <0 try again later
623 */
nua_dialog_usage_shutdown(nua_owner_t * owner,nua_dialog_state_t * ds,nua_dialog_usage_t * du)624 int nua_dialog_usage_shutdown(nua_owner_t *owner,
625 nua_dialog_state_t *ds,
626 nua_dialog_usage_t *du)
627 {
628 if (du) {
629 nua_dialog_usage_reset_refresh(du);
630 du->du_shutdown = 1;
631 assert(du->du_class->usage_shutdown);
632 return du->du_class->usage_shutdown(owner, ds, du);
633 }
634 else
635 return 200;
636 }
637
638 /** Repeat shutdown of all usages.
639 *
640 * @note Caller must have a reference to nh
641 */
nua_dialog_repeat_shutdown(nua_owner_t * owner,nua_dialog_state_t * ds)642 int nua_dialog_repeat_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds)
643 {
644 nua_dialog_usage_t *du;
645 nua_server_request_t *sr, *sr_next;
646
647 for (sr = ds->ds_sr; sr; sr = sr_next) {
648 sr_next = sr->sr_next;
649
650 if (nua_server_request_is_pending(sr)) {
651 SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */
652 nua_server_respond(sr, NULL);
653 nua_server_report(sr);
654 }
655 }
656
657 for (du = ds->ds_usage; du ;) {
658 nua_dialog_usage_t *du_next = du->du_next;
659
660 nua_dialog_usage_shutdown(owner, ds, du);
661
662 if (du_next == NULL)
663 break;
664
665 for (du = ds->ds_usage; du; du = du->du_next) {
666 if (du == du_next)
667 break;
668 else if (!du->du_shutdown)
669 break;
670 }
671 }
672
673 return ds->ds_usage != NULL;
674 }
675