/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 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 nea.c Nokia Event Client API agent implementation. * * @author Pekka Pessi * * @date Created: Wed Feb 14 18:32:58 2001 ppessi */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #define SU_TIMER_ARG_T struct nea_s #define NTA_LEG_MAGIC_T struct nea_s #define NTA_OUTGOING_MAGIC_T struct nea_s #define NEA_TIMER_DELTA 2 /* time to resubscribe without expiration */ #define EXPIRES_DEFAULT 3600 #include #include "sofia-sip/nea.h" struct nea_s { su_home_t nea_home[1]; su_timer_t *nea_timer; nta_agent_t *nea_agent; nta_leg_t *nea_leg; nta_outgoing_t *nea_oreq; /**< Outstanding request */ sip_to_t *nea_to; /**< The other end of subscription :) */ nea_notify_f nea_callback; /**< Notify callback */ nea_magic_t *nea_context; /**< Application context */ sip_contact_t *nea_contact; /**< */ sip_expires_t *nea_expires; /**< Proposed expiration time */ nea_state_t nea_state; /**< State of our subscription */ sip_time_t nea_deadline; /**< When our subscription expires */ tagi_t *nea_args; unsigned nea_dialog : 1; /**< Dialog has been established */ unsigned nea_notify_received : 1; unsigned nea_terminating : 1; unsigned nea_strict_3265 : 1; /**< Strict mode */ }; int details = 0; static int process_nea_request(nea_t *nea, nta_leg_t *leg, nta_incoming_t *ireq, sip_t const *sip); static int handle_notify(nta_leg_magic_t *lmagic, nta_leg_t *leg, nta_incoming_t *ireq, sip_t const *sip); static int response_to_subscribe(nea_t *nea, nta_outgoing_t *req, sip_t const *sip); static int response_to_unsubscribe(nea_t *nea, nta_outgoing_t *req, sip_t const *sip); static void nea_expires_renew(su_root_magic_t *magic, su_timer_t *timer, nea_t *nea); /* ---------------------------------------------------------- */ /** Create a event watcher object. * */ nea_t *nea_create(nta_agent_t *agent, su_root_t *root, nea_notify_f no_callback, nea_magic_t *context, tag_type_t tag, tag_value_t value, ...) { nea_t *nea = NULL; ta_list ta; int have_from, have_to, have_contact; sip_expires_t const *expires = NULL; char const *expires_str = NULL; sip_method_t method = sip_method_subscribe; char const *SUBSCRIBE = "SUBSCRIBE"; char const *method_name = SUBSCRIBE; ta_start(ta, tag, value); have_to = tl_find(ta_args(ta), siptag_to) || tl_find(ta_args(ta), siptag_to_str); have_from = tl_find(ta_args(ta), siptag_from) || tl_find(ta_args(ta), siptag_from_str); have_contact = tl_find(ta_args(ta), siptag_contact) || tl_find(ta_args(ta), siptag_contact_str); if (have_to && (nea = su_home_new(sizeof(nea_t)))) { su_home_t *home = nea->nea_home; sip_contact_t *m = nta_agent_contact(agent); sip_from_t *from; sip_to_t const *to; int strict = 0; nea->nea_agent = agent; nea->nea_callback = no_callback; nea->nea_context = context; if (!have_from) from = sip_from_create(home, (url_string_t*)m->m_url); else from = NULL; nea->nea_args = tl_tlist(home, TAG_IF(!have_contact, SIPTAG_CONTACT(m)), ta_tags(ta)); /* Get and remove Expires header from tag list */ tl_gets(nea->nea_args, SIPTAG_EXPIRES_REF(expires), SIPTAG_EXPIRES_STR_REF(expires_str), SIPTAG_TO_REF(to), NEATAG_STRICT_3265_REF(strict), NTATAG_METHOD_REF(method_name), TAG_END()); nea->nea_strict_3265 = strict; if (to) nea->nea_to = sip_to_dup(home, to); if (expires) nea->nea_expires = sip_expires_dup(home, expires); else if (expires_str) nea->nea_expires = sip_expires_make(home, expires_str); else nea->nea_expires = sip_expires_create(home, EXPIRES_DEFAULT); tl_tremove(nea->nea_args, SIPTAG_EXPIRES(0), SIPTAG_EXPIRES_STR(0), TAG_END()); if (method_name != SUBSCRIBE) method = sip_method_code(method_name); if (method != sip_method_invalid) /* Create the timer object */ nea->nea_timer = su_timer_create(su_root_task(root), 0L); if (nea->nea_timer) { /* Create leg for NOTIFY requests */ nea->nea_leg = nta_leg_tcreate(nea->nea_agent, process_nea_request, nea, TAG_IF(!have_from, SIPTAG_FROM(from)), TAG_NEXT(nea->nea_args)); if (nea->nea_leg) { nta_leg_tag(nea->nea_leg, NULL); nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_subscribe, nea, NULL, method, method_name, NULL, SIPTAG_EXPIRES(nea->nea_expires), TAG_NEXT(nea->nea_args)); } } if (!nea->nea_leg || !nea->nea_oreq || !nea->nea_timer) nea_destroy(nea), nea = NULL; } ta_end(ta); return nea; } int nea_update(nea_t *nea, tag_type_t tag, tag_value_t value, ...) { ta_list ta; sip_expires_t const *expires = NULL; sip_payload_t const *pl = NULL; sip_content_type_t const *ct = NULL; char const *cts = NULL; /* char const *expires_str = NULL; */ su_home_t *home = nea->nea_home; /* XXX - hack, previous request still waiting for response */ if (!nea->nea_leg || nea->nea_oreq) return -1; ta_start(ta, tag, value); tl_gets(ta_args(ta), SIPTAG_CONTENT_TYPE_REF(ct), SIPTAG_CONTENT_TYPE_STR_REF(cts), SIPTAG_PAYLOAD_REF(pl), SIPTAG_EXPIRES_REF(expires), TAG_NULL()); if (!pl || (!ct && !cts)) { ta_end(ta); return -1; } tl_tremove(nea->nea_args, SIPTAG_CONTENT_TYPE(0), SIPTAG_CONTENT_TYPE_STR(0), SIPTAG_PAYLOAD(0), SIPTAG_PAYLOAD_STR(0), TAG_END()); su_free(home, nea->nea_expires); if (expires) nea->nea_expires = sip_expires_dup(home, expires); else nea->nea_expires = sip_expires_create(home, EXPIRES_DEFAULT); /* nta_leg_tag(nea->nea_leg, NULL); */ nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_subscribe, nea, NULL, SIP_METHOD_SUBSCRIBE, NULL, SIPTAG_TO(nea->nea_to), SIPTAG_PAYLOAD(pl), TAG_IF(ct, SIPTAG_CONTENT_TYPE(ct)), TAG_IF(cts, SIPTAG_CONTENT_TYPE_STR(cts)), SIPTAG_EXPIRES(nea->nea_expires), TAG_NEXT(nea->nea_args)); ta_end(ta); if (!nea->nea_oreq) return -1; return 0; } /** Unsubscribe the agent. */ void nea_end(nea_t *nea) { if (nea == NULL) return; nea->nea_terminating = 1; su_timer_destroy(nea->nea_timer), nea->nea_timer = NULL; if (nea->nea_leg && nea->nea_deadline) { nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_unsubscribe, nea, NULL, SIP_METHOD_SUBSCRIBE, NULL, SIPTAG_EXPIRES_STR("0"), TAG_NEXT(nea->nea_args)); } } void nea_destroy(nea_t *nea) { if (nea == NULL) return; if (nea->nea_oreq) nta_outgoing_destroy(nea->nea_oreq), nea->nea_oreq = NULL; if (nea->nea_leg) nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL; if (nea->nea_timer) { su_timer_reset(nea->nea_timer); su_timer_destroy(nea->nea_timer), nea->nea_timer = NULL; } su_free(NULL, nea); } /* Function called by NTA to handle incoming requests belonging to the leg */ int process_nea_request(nea_t *nea, nta_leg_t *leg, nta_incoming_t *ireq, sip_t const *sip) { switch (sip->sip_request->rq_method) { case sip_method_notify: return handle_notify(nea, leg, ireq, sip); case sip_method_ack: return 400; default: nta_incoming_treply(ireq, SIP_405_METHOD_NOT_ALLOWED, SIPTAG_ALLOW_STR("NOTIFY"), TAG_END()); return 405; } } /* Callback function to handle subscription requests */ int response_to_subscribe(nea_t *nea, nta_outgoing_t *oreq, sip_t const *sip) { int status = sip->sip_status->st_status; int error = status >= 300; if (status >= 200 && oreq == nea->nea_oreq) nea->nea_oreq = NULL; nea->nea_callback(nea, nea->nea_context, sip); if (status < 200) return 0; nea->nea_oreq = NULL; if (status < 300) { sip_time_t now = sip_now(); if (!nea->nea_notify_received) { nea->nea_deadline = now + sip_contact_expires(NULL, sip->sip_expires, sip->sip_date, EXPIRES_DEFAULT, now); if (sip->sip_to->a_tag && !nea->nea_dialog) { nea->nea_dialog = 1; nta_leg_rtag(nea->nea_leg, sip->sip_to->a_tag); nta_leg_client_route(nea->nea_leg, sip->sip_record_route, sip->sip_contact); } } } else { nea->nea_deadline = 0; nea->nea_state = nea_terminated; if (status == 301 || status == 302 || status == 305) { sip_contact_t *m; for (m = sip->sip_contact; m; m = m->m_next) { if (m->m_url->url_type == url_sip || m->m_url->url_type == url_sips) break; } if (m) { url_string_t const *proxy, *url; if (status == 305) url = NULL, proxy = (url_string_t *)m->m_url; else url = (url_string_t *)m->m_url, proxy = NULL; nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_subscribe, nea, proxy, SIP_METHOD_SUBSCRIBE, url, SIPTAG_EXPIRES(nea->nea_expires), TAG_NEXT(nea->nea_args)); } } else if (status == 423 && sip->sip_min_expires) { unsigned value = sip->sip_min_expires->me_delta; su_free(nea->nea_home, nea->nea_expires); nea->nea_expires = sip_expires_format(nea->nea_home, "%u", value); nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_subscribe, nea, NULL, SIP_METHOD_SUBSCRIBE, NULL, SIPTAG_EXPIRES(nea->nea_expires), TAG_NEXT(nea->nea_args)); } } if (status >= 200) nta_outgoing_destroy(oreq); if (nea->nea_oreq || !error) { su_time_t now = su_now(); now.tv_sec = nea->nea_deadline; su_timer_set_at(nea->nea_timer, nea_expires_renew, nea, now); } else nea->nea_callback(nea, nea->nea_context, NULL); return 0; } int response_to_unsubscribe(nea_t *nea, nta_outgoing_t *orq, sip_t const *sip) { int status = sip->sip_status->st_status; nea->nea_callback(nea, nea->nea_context, sip); if (status >= 200) nta_outgoing_destroy(orq), nea->nea_oreq = NULL; if (status >= 300) nea->nea_callback(nea, nea->nea_context, NULL); return 0; } /** handle notifications */ int handle_notify(nea_t *nea, nta_leg_t *leg, nta_incoming_t *irq, sip_t const *sip) { sip_subscription_state_t *ss = sip->sip_subscription_state; sip_subscription_state_t ss0[1]; char expires[32]; if (nea->nea_strict_3265) { char const *phrase = NULL; if (ss == NULL) phrase = "NOTIFY Has No Subscription-State Header"; else if (sip->sip_event == NULL) phrase = "Event Header Missing"; if (phrase) { nta_incoming_treply(irq, 400, phrase, TAG_END()); nta_incoming_destroy(irq); nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL; nea->nea_state = nea_terminated; nea->nea_callback(nea, nea->nea_context, NULL); return 0; } } if (ss == NULL) { /* Do some compatibility stuff here */ unsigned long delta = 3600; sip_subscription_state_init(ss = ss0); if (sip->sip_expires) delta = sip->sip_expires->ex_delta; if (delta == 0) ss->ss_substate = "terminated"; else ss->ss_substate = "active"; if (delta > 0) { snprintf(expires, sizeof expires, "%lu", delta); ss->ss_expires = expires; } } if (!nea->nea_dialog) { nea->nea_dialog = 1; nta_leg_rtag(nea->nea_leg, sip->sip_from->a_tag); nta_leg_server_route(nea->nea_leg, sip->sip_record_route, sip->sip_contact); } nea->nea_notify_received = 1; nea->nea_callback(nea, nea->nea_context, sip); if (strcasecmp(ss->ss_substate, "terminated") == 0) { nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL; nea->nea_state = nea_terminated; if (str0casecmp(ss->ss_reason, "deactivated") == 0) { nea->nea_state = nea_embryonic; nea->nea_deadline = sip_now(); } else if (str0casecmp(ss->ss_reason, "probation") == 0) { sip_time_t retry = sip_now() + NEA_TIMER_DELTA; if (ss->ss_retry_after) retry += strtoul(ss->ss_retry_after, NULL, 10); else retry += NEA_TIMER_DELTA; nea->nea_state = nea_embryonic; nea->nea_deadline = retry; } else { nea->nea_deadline = 0; nea->nea_callback(nea, nea->nea_context, NULL); return 200; } } else if (strcasecmp(ss->ss_substate, "pending") == 0) nea->nea_state = nea_pending; else if (strcasecmp(ss->ss_substate, "active") == 0) nea->nea_state = nea_active; else nea->nea_state = nea_extended; if (nea->nea_state != nea_embryonic && ss->ss_expires) { unsigned retry = strtoul(ss->ss_expires, NULL, 10); if (retry > 60) retry -= 30; else retry /= 2; nea->nea_deadline = sip_now() + retry; } { su_time_t now = su_now(); now.tv_sec = nea->nea_deadline; su_timer_set_at(nea->nea_timer, nea_expires_renew, nea, now); } return 200; } void nea_expires_renew(su_root_magic_t *magic, su_timer_t *timer, nea_t *nea) { sip_time_t now = sip_now(); /* re-subscribe if expires soon */ if (nea->nea_state == nea_terminated || nea->nea_deadline == 0 || nea->nea_deadline > now + NEA_TIMER_DELTA) return; if (!nea->nea_notify_received) /* Hmph. */ return; nea->nea_notify_received = 0; nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg, response_to_subscribe, nea, NULL, SIP_METHOD_SUBSCRIBE, NULL, SIPTAG_EXPIRES(nea->nea_expires), TAG_NEXT(nea->nea_args)); return; }