/* * This file is part of the Sofia-SIP package * * Copyright (C) 2006 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@CFILE nua_dialog.c * @brief Dialog and dialog usage handling * * @author Pekka Pessi * * @date Created: Wed Mar 8 11:48:49 EET 2006 ppessi */ #include "config.h" #include #include #include #include #include #include #include #include #include #define NUA_OWNER_T su_home_t #include "nua_dialog.h" #define SU_LOG (nua_log) #include #ifndef NONE #ifndef _MSC_VER #define NONE ((void *)-1) #else #define NONE ((void *)(INT_PTR)-1) #endif #endif /* ======================================================================== */ /* Dialog handling */ static void nua_dialog_usage_remove_at(nua_owner_t*, nua_dialog_state_t*, nua_dialog_usage_t**, nua_client_request_t *cr, nua_server_request_t *sr); static void nua_dialog_log_usage(nua_owner_t *, nua_dialog_state_t *); /**@internal * UAS tag and route. * * Update dialog tags and route on the UAS side. * * @param own dialog owner * @param ds dialog state * @param sip SIP message containing response used to update dialog * @param rtag if true, set remote tag within the leg */ void nua_dialog_uas_route(nua_owner_t *own, nua_dialog_state_t *ds, sip_t const *sip, int rtag) { int established = nua_dialog_is_established(ds); if (!established && sip->sip_from->a_tag) ds->ds_remote_tag = su_strdup(own, sip->sip_from->a_tag); if (ds->ds_leg == NULL) return; nta_leg_server_route(ds->ds_leg, sip->sip_record_route, sip->sip_contact); ds->ds_route = ds->ds_route || sip->sip_record_route || sip->sip_contact; if (rtag && !established && sip->sip_from->a_tag) nta_leg_rtag(ds->ds_leg, sip->sip_from->a_tag); } /**@internal * UAC tag and route. * * Update dialog tags and route on the UAC side. * * @param own dialog owner * @param ds dialog state * @param sip SIP message containing response used to update dialog * @param rtag if true, set remote tag within the leg * @param initial if true, @a sip is response to initial transaction */ void nua_dialog_uac_route(nua_owner_t *own, nua_dialog_state_t *ds, sip_t const *sip, int rtag, int initial) { int established = nua_dialog_is_established(ds); int status = sip->sip_status->st_status; if (!established && sip->sip_to->a_tag) ds->ds_remote_tag = su_strdup(own, sip->sip_to->a_tag); if (ds->ds_leg == NULL) return; if (initial && status >= 200) nta_leg_client_reroute(ds->ds_leg, sip->sip_record_route, sip->sip_contact, 1); else nta_leg_client_reroute(ds->ds_leg, sip->sip_record_route, sip->sip_contact, 0); ds->ds_route = ds->ds_route || sip->sip_record_route || sip->sip_contact; if (rtag && !established && sip->sip_to->a_tag) nta_leg_rtag(ds->ds_leg, sip->sip_to->a_tag); } /**@internal Store information from remote endpoint. */ void nua_dialog_store_peer_info(nua_owner_t *own, nua_dialog_state_t *ds, sip_t const *sip) { nua_dialog_peer_info_t *nr = ds->ds_remote_ua; nua_dialog_usage_t *du; nua_dialog_peer_info_t old[1]; *old = *nr; if (sip && sip->sip_status && sip->sip_status->st_status >= 300 && sip->sip_status->st_status <= 399) sip = NULL; /* Redirected */ if (sip == NULL) { nr->nr_via = NULL, su_free(own, old->nr_via); nr->nr_allow = NULL, su_free(own, old->nr_allow); nr->nr_accept = NULL, su_free(own, old->nr_accept); nr->nr_require = NULL, su_free(own, old->nr_require); nr->nr_supported = NULL, su_free(own, old->nr_supported); nr->nr_user_agent = NULL, su_free(own, old->nr_user_agent); return; } if (sip->sip_allow) { nr->nr_allow = sip_allow_dup(own, sip->sip_allow); su_free(own, old->nr_allow); } if (sip->sip_accept) { nr->nr_accept = sip_accept_dup(own, sip->sip_accept); su_free(own, old->nr_accept); } if (sip->sip_require) { nr->nr_require = sip_require_dup(own, sip->sip_require); su_free(own, old->nr_require); } if (sip->sip_supported) { nr->nr_supported = sip_supported_dup(own, sip->sip_supported); su_free(own, old->nr_supported); } if (sip->sip_via) { nr->nr_via = sip_via_dup(own, sip->sip_via); su_free(own, old->nr_via); } if (sip->sip_user_agent) { nr->nr_user_agent = sip_user_agent_dup(own, sip->sip_user_agent); su_free(own, old->nr_user_agent); } else if (sip->sip_server) { nr->nr_user_agent = sip_user_agent_dup(own, sip->sip_server); su_free(own, old->nr_user_agent); } for (du = ds->ds_usage; du; du = du->du_next) { if (du->du_class->usage_peer_info) du->du_class->usage_peer_info(du, ds, sip); } } /** Remove dialog information. */ int nua_dialog_zap(nua_owner_t *own, nua_dialog_state_t *ds) { /* zap peer info */ nua_dialog_store_peer_info(own, ds, NULL); /* Local Contact */ msg_header_free(own, (msg_header_t *)ds->ds_ltarget), ds->ds_ltarget = NULL; /* Leg */ nta_leg_destroy(ds->ds_leg), ds->ds_leg = NULL; /* Remote tag */ su_free(own, (void *)ds->ds_remote_tag), ds->ds_remote_tag = NULL; /* Ready to set route/remote target */ ds->ds_route = 0; return 0; } /** Remove dialog (if there is no other usages). */ int nua_dialog_remove(nua_owner_t *own, nua_dialog_state_t *ds, nua_dialog_usage_t *usage) { if (ds->ds_usage == usage && (usage == NULL || usage->du_next == NULL)) { return nua_dialog_zap(own, ds); } return 0; } /** @internal Get dialog usage slot. */ nua_dialog_usage_t ** nua_dialog_usage_at(nua_dialog_state_t const *ds, nua_usage_class const *kind, sip_event_t const *event) { static nua_dialog_usage_t *none = NULL; if (ds) { nua_dialog_usage_t *du, * const * prev; sip_event_t const *o; for (prev = &ds->ds_usage; (du = *prev); prev = &du->du_next) { if (du->du_class != kind) continue; if (event == NONE) return (nua_dialog_usage_t **)prev; o = du->du_event; if (!event && !o) return (nua_dialog_usage_t **)prev; if (event != o) { if (event == NULL || o == NULL) continue; if (!su_strmatch(event->o_type, o->o_type)) continue; if (!su_casematch(event->o_id, o->o_id)) { if (event->o_id || !su_strmatch(event->o_type, "refer")) continue; } } return (nua_dialog_usage_t **)prev; } } return &none; } /** @internal Get a dialog usage */ nua_dialog_usage_t *nua_dialog_usage_get(nua_dialog_state_t const *ds, nua_usage_class const *kind, sip_event_t const *event) { return *nua_dialog_usage_at(ds, kind, event); } /** @internal Get dialog usage name */ char const *nua_dialog_usage_name(nua_dialog_usage_t const *du) { if (du == NULL) return ""; return du->du_class->usage_name(du); } /** @internal Add dialog usage */ nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own, struct nua_dialog_state *ds, nua_usage_class const *uclass, sip_event_t const *event) { if (ds) { sip_event_t *o; nua_dialog_usage_t *du, **prev_du; prev_du = nua_dialog_usage_at(ds, uclass, event); du = *prev_du; if (du) { /* Already exists */ SU_DEBUG_5(("nua(%p): adding already existing %s usage%s%s\n", (void *)own, nua_dialog_usage_name(du), event ? " with event " : "", event ? event->o_type : "")); if (prev_du != &ds->ds_usage) { /* Move as a first usage in the list */ *prev_du = du->du_next; du->du_next = ds->ds_usage; ds->ds_usage = du; } return du; } o = event ? sip_event_dup(own, event) : NULL; if (o != NULL || event == NULL) du = su_zalloc(own, sizeof *du + uclass->usage_size); if (du) { su_home_ref(own); du->du_dialog = ds; du->du_class = uclass; du->du_event = o; if (uclass->usage_add(own, ds, du) < 0) { su_free(own, o); su_free(own, du); return NULL; } SU_DEBUG_5(("nua(%p): adding %s usage%s%s\n", (void *)own, nua_dialog_usage_name(du), o ? " with event " : "", o ? o->o_type :"")); du->du_next = ds->ds_usage, ds->ds_usage = du; return du; } su_free(own, o); } return NULL; } /** @internal Remove dialog usage. */ void 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) { nua_dialog_usage_t **at; assert(own); assert(ds); assert(du); for (at = &ds->ds_usage; *at; at = &(*at)->du_next) if (du == *at) break; assert(*at); nua_dialog_usage_remove_at(own, ds, at, cr, sr); } /** @internal Remove dialog usage. * * Zap dialog state (leg, tag and route) if no usages remain. */ 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) { int unref = 0; nua_dialog_usage_t *du = NULL; if (*at) { sip_event_t const *o = NULL; nua_client_request_t *cr, *cr_next; nua_server_request_t *sr, *sr_next; du = *at; *at = du->du_next; o = du->du_event; SU_DEBUG_5(("nua(%p): removing %s usage%s%s\n", (void *)own, nua_dialog_usage_name(du), o ? " with event " : "", o ? o->o_type :"")); du->du_class->usage_remove(own, ds, du, cr0, sr0); /* Clean reference to saved client request */ if (du->du_cr) nua_client_bind(du->du_cr, NULL); /* Clean references from queued client requests */ for (cr = ds->ds_cr; cr; cr = cr_next) { cr_next = cr->cr_next; if (cr->cr_usage == du) cr->cr_usage = NULL; } /* Clean references from queued server requests */ for (sr = ds->ds_sr; sr; sr = sr_next) { sr_next = sr->sr_next; if (sr->sr_usage == du) { sr->sr_usage = NULL; if (sr != sr0) nua_server_request_destroy(sr); } } unref = 1; } /* Zap dialog if there are no more usages */ if (ds->ds_terminating) ; else if (ds->ds_usage == NULL) { nua_dialog_remove(own, ds, NULL); ds->ds_has_events = 0; if (unref) { su_home_unref(own); su_free(own, du); } return; } else { nua_dialog_log_usage(own, ds); } if (unref) { su_home_unref(own); su_free(own, du); } } static void nua_dialog_log_usage(nua_owner_t *own, nua_dialog_state_t *ds) { nua_dialog_usage_t *du; if (SU_LOG->log_level >= 3) { char buffer[160]; size_t l = 0, N = sizeof buffer; ssize_t n; buffer[0] = '\0'; for (du = ds->ds_usage; du; du = du->du_next) { msg_header_t const *h = (void *)du->du_event; if (!h) continue; n = sip_event_e(buffer + l, N - l, h, 0); if (n == -1) break; l += (size_t)n; if (du->du_next && l + 2 < sizeof(buffer)) { strcpy(buffer + l, ", "); l += 2; } } SU_DEBUG_3(("nua(%p): handle with %s%s%s\n", (void *)own, ds->ds_has_session ? "session and " : "", ds->ds_has_events ? "events " : "", buffer)); } } /** Deinitialize dialog and its usage. @internal */ void nua_dialog_deinit(nua_owner_t *own, nua_dialog_state_t *ds) { ds->ds_terminating = 1; while (ds->ds_usage) { nua_dialog_usage_remove_at(own, ds, &ds->ds_usage, NULL, NULL); } nua_dialog_remove(own, ds, NULL); ds->ds_has_events = 0; ds->ds_terminating = 0; } void 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) { nua_dialog_usage_t *usage; for (usage = ds->ds_usage; usage; usage = usage->du_next) { usage->du_class->usage_update_params(usage, changed, params, defaults); } } void 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) { (void)du, (void)changed, (void)params, (void)defaults; } /**@internal * Set refresh value suitably. * * The refresh time is set either around half of the @a delta interval or, * if @a delta is less than 5 minutes but longer than 90 seconds, 30..60 * seconds before end of interval. * * If @a delta is 0, the dialog usage is never refreshed. */ void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta) { if (delta == 0) nua_dialog_usage_reset_refresh(du); else if (delta > 90 && delta < 5 * 60) /* refresh 30..60 seconds before deadline */ nua_dialog_usage_set_refresh_range(du, delta - 60, delta - 30); else { /* By default, refresh around half time before deadline */ unsigned min = (delta + 2) / 4; unsigned max = (delta + 2) / 4 + (delta + 1) / 2; if (min == 0) min = 1; nua_dialog_usage_set_refresh_range(du, min, max); } } /**@internal Set refresh in range min..max seconds in the future. */ void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du, unsigned min, unsigned max) { sip_time_t now = sip_now(), target; unsigned delta; if (max < min) max = min; if (min != max) delta = su_randint(min, max); else delta = min; if (now + delta >= now) target = now + delta; else target = SIP_TIME_MAX; SU_DEBUG_7(("nua(): refresh %s after %lu seconds (in [%u..%u])\n", nua_dialog_usage_name(du), target - now, min, max)); du->du_refquested = now; du->du_refresh = target; } /** Set absolute refresh time */ void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du, sip_time_t target) { SU_DEBUG_7(("nua(): refresh %s after %lu seconds\n", nua_dialog_usage_name(du), target - sip_now())); du->du_refresh = target; } /**@internal Do not refresh. */ void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du) { if (du) { du->du_refquested = sip_now(); du->du_refresh = 0; } } /** @internal Refresh usage. */ void nua_dialog_usage_refresh(nua_owner_t *owner, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now) { assert(du && du->du_class->usage_refresh); du->du_class->usage_refresh(owner, ds, du, now); } /** Terminate all dialog usages gracefully. */ int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds) { nua_dialog_usage_t *du; ds->ds_terminating = 1; do { for (du = ds->ds_usage; du; du = du->du_next) { if (!du->du_shutdown) { nua_dialog_usage_shutdown(owner, ds, du); break; } } } while (du); return 1; } /** Shutdown (gracefully terminate) usage. * * @retval >0 shutdown done * @retval 0 shutdown in progress * @retval <0 try again later */ int nua_dialog_usage_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { if (du) { nua_dialog_usage_reset_refresh(du); du->du_shutdown = 1; assert(du->du_class->usage_shutdown); return du->du_class->usage_shutdown(owner, ds, du); } else return 200; } /** Repeat shutdown of all usages. * * @note Caller must have a reference to nh */ int nua_dialog_repeat_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds) { nua_dialog_usage_t *du; nua_server_request_t *sr, *sr_next; for (sr = ds->ds_sr; sr; sr = sr_next) { sr_next = sr->sr_next; if (nua_server_request_is_pending(sr)) { SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */ nua_server_respond(sr, NULL); if (nua_server_report(sr) > 2) { /* Dialog was destroyed if nua_base_server_report() returned 3 or 4. All references (sr) are cleared from queued server requests at this point. See nua_dialog_deinit() and nua_dialog_usage_remove_at() */ break; } } } for (du = ds->ds_usage; du ;) { nua_dialog_usage_t *du_next = du->du_next; nua_dialog_usage_shutdown(owner, ds, du); if (du_next == NULL) break; for (du = ds->ds_usage; du; du = du->du_next) { if (du == du_next) break; else if (!du->du_shutdown) break; } } return ds->ds_usage != NULL; }