598 lines
15 KiB
C
598 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/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 (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;
|
|
}
|