freeswitch/libs/sofia-sip/libsofia-sip-ua/nea/nea.c

599 lines
15 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* 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 <Pekka.Pessi@nokia.com>
*
* @date Created: Wed Feb 14 18:32:58 2001 ppessi
*/
#include "config.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_string.h>
#include <sofia-sip/sip.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/sip_status.h>
#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 <sofia-sip/su_wait.h>
#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 (su_casematch(ss->ss_substate, "terminated")) {
nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL;
nea->nea_state = nea_terminated;
if (su_casematch(ss->ss_reason, "deactivated")) {
nea->nea_state = nea_embryonic;
nea->nea_deadline = sip_now();
} else if (su_casematch(ss->ss_reason, "probation")) {
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 (su_casematch(ss->ss_substate, "pending"))
nea->nea_state = nea_pending;
else if (su_casematch(ss->ss_substate, "active"))
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;
}