freeswitch/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c

1009 lines
30 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2006 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 nua_notifier.c
* @brief SUBSCRIBE server, NOTIFY client and REFER server
*
* Simpler event server. See nua_event_server.c for more complex event
* server.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Wed Mar 8 15:10:08 EET 2006 ppessi
*/
#include "config.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include <sofia-sip/string0.h>
#include <sofia-sip/sip_protos.h>
#include <sofia-sip/sip_extra.h>
#include <sofia-sip/sip_status.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/su_uniqueid.h>
#include <sofia-sip/su_md5.h>
#include <sofia-sip/token64.h>
#include "nua_stack.h"
/* ---------------------------------------------------------------------- */
/* Notifier event usage */
struct notifier_usage
{
enum nua_substate nu_substate; /**< Subscription state */
sip_time_t nu_expires; /**< Expiration time */
sip_time_t nu_requested; /**< Requested expiration time */
#if SU_HAVE_EXPERIMENTAL
char *nu_tag; /**< @ETag in last NOTIFY */
unsigned nu_etags:1; /**< Subscriber supports etags */
unsigned nu_appl_etags:1; /**< Application generates etags */
unsigned nu_no_body:1; /**< Suppress body */
#endif
};
static char const *nua_notify_usage_name(nua_dialog_usage_t const *du);
static int nua_notify_usage_add(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du);
static void nua_notify_usage_remove(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du,
nua_client_request_t *cr,
nua_server_request_t *sr);
static void nua_notify_usage_refresh(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du,
sip_time_t now);
static int nua_notify_usage_shutdown(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du);
static nua_usage_class const nua_notify_usage[1] = {
{
sizeof (struct notifier_usage), (sizeof nua_notify_usage),
nua_notify_usage_add,
nua_notify_usage_remove,
nua_notify_usage_name,
nua_base_usage_update_params,
NULL,
nua_notify_usage_refresh,
nua_notify_usage_shutdown,
}};
static char const *nua_notify_usage_name(nua_dialog_usage_t const *du)
{
return "notify";
}
static
int nua_notify_usage_add(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du)
{
ds->ds_has_events++;
ds->ds_has_notifys++;
return 0;
}
static
void nua_notify_usage_remove(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du,
nua_client_request_t *cr,
nua_server_request_t *sr)
{
ds->ds_has_events--;
ds->ds_has_notifys--;
}
/* ====================================================================== */
/* SUBSCRIBE server */
/** @NUA_EVENT nua_i_subscribe
*
* Incoming @b SUBSCRIBE request.
*
* @b SUBSCRIBE request is used to query SIP event state or establish a SIP
* event subscription.
*
* @param status status code of response sent automatically by stack
* @param phrase response phrase sent automatically by stack
* @param nh operation handle associated with the incoming request
* @param hmagic application context associated with the handle
* (NULL when handle is created by the stack)
* @param sip SUBSCRIBE request headers
* @param tags NUTAG_SUBSTATE()
*
* Initial SUBSCRIBE requests are dropped with <i>489 Bad Event</i>
* response, unless the application has explicitly included the @Event in
* the list of allowed events with nua_set_params() tag NUTAG_ALLOW_EVENTS()
* (or SIPTAG_ALLOW_EVENTS() or SIPTAG_ALLOW_EVENTS_STR()).
*
* If the event has been allowed the application
* can decide whether to accept the SUBSCRIBE request or reject it. The
* nua_response() call responding to a SUBSCRIBE request must have
* NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag.
*
* If the application accepts the SUBSCRIBE request, it must immediately
* send an initial NOTIFY establishing the dialog. This is because the
* response to the SUBSCRIBE request may be lost by an intermediate proxy
* because it had forked the SUBSCRIBE request.
*
* SUBSCRIBE requests modifying (usually refreshing or terminating) an
* existing event subscription are accepted by default and a <i>200 OK</i>
* response along with a copy of previously sent NOTIFY is sent
* automatically to the subscriber.
*
* By default, only event subscriptions accepted are those created
* implicitly by REFER request. See #nua_i_refer how the application must
* handle the REFER requests.
*
* @par Subscription Lifetime and Terminating Subscriptions
*
* Accepting the SUBSCRIBE request creates a dialog with a <i>notifier
* dialog usage</i> on the handle. The dialog usage is active, until the
* subscriber terminates the subscription, it times out or the application
* terminates the usage with nua_notify() call containing the tag
* NUTAG_SUBSTATE(nua_substate_terminated) or @SubscriptionState header with
* state "terminated" and/or expiration time 0.
*
* When the subscriber terminates the subscription, the application is
* notified of an termination by a #nua_i_subscribe event with
* NUTAG_SUBSTATE(nua_substate_terminated) tag. When the subscription times
* out, nua automatically initiates a NOTIFY transaction. When it is
* terminated, the application is sent a #nua_r_notify event with
* NUTAG_SUBSTATE(nua_substate_terminated) tag.
*
* @sa @RFC3265, nua_notify(), NUTAG_SUBSTATE(), @SubscriptionState,
* @Event, nua_subscribe(), #nua_r_subscribe, #nua_i_refer, nua_refer()
*
* @END_NUA_EVENT
*/
static int nua_subscribe_server_init(nua_server_request_t *sr);
static int nua_subscribe_server_preprocess(nua_server_request_t *sr);
static int nua_subscribe_server_respond(nua_server_request_t*, tagi_t const *);
static int nua_subscribe_server_report(nua_server_request_t*, tagi_t const *);
nua_server_methods_t const nua_subscribe_server_methods =
{
SIP_METHOD_SUBSCRIBE,
nua_i_subscribe, /* Event */
{
1, /* Create dialog */
0, /* Initial request */
1, /* Target refresh request */
1, /* Add Contact */
},
nua_subscribe_server_init,
nua_subscribe_server_preprocess,
nua_base_server_params,
nua_subscribe_server_respond,
nua_subscribe_server_report,
};
int nua_subscribe_server_init(nua_server_request_t *sr)
{
nua_handle_t *nh = sr->sr_owner;
nua_dialog_state_t *ds = nh->nh_ds;
sip_allow_events_t const *allow_events = NH_PGET(nh, allow_events);
sip_t const *sip = sr->sr_request.sip;
sip_event_t *o = sip->sip_event;
char const *event = o ? o->o_type : NULL;
if (sr->sr_initial || !nua_dialog_usage_get(ds, nua_notify_usage, o)) {
if (event && str0cmp(event, "refer") == 0)
/* refer event subscription should be initiated with REFER */
return SR_STATUS1(sr, SIP_403_FORBIDDEN);
/* XXX - event is case-sensitive, should use msg_header_find_item() */
if (!event || !msg_header_find_param(allow_events->k_common, event))
return SR_STATUS1(sr, SIP_489_BAD_EVENT);
}
return 0;
}
int nua_subscribe_server_preprocess(nua_server_request_t *sr)
{
nua_handle_t *nh = sr->sr_owner;
nua_dialog_state_t *ds = nh->nh_ds;
nua_dialog_usage_t *du;
struct notifier_usage *nu;
sip_t const *sip = sr->sr_request.sip;
sip_event_t *o = sip->sip_event;
char const *event = o ? o->o_type : NULL;
/* Maximum expiration time */
unsigned long expires = sip->sip_expires ? sip->sip_expires->ex_delta : 3600;
sip_time_t now = sip_now();
assert(nh && nh->nh_nua->nua_dhandle != nh);
du = nua_dialog_usage_get(ds, nua_notify_usage, o);
if (du == NULL) {
/* Create a new subscription */
du = nua_dialog_usage_add(nh, ds, nua_notify_usage, o);
if (du == NULL)
return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
}
else {
/* Refresh existing subscription */
if (str0cmp(event, "refer") == 0)
expires = NH_PGET(nh, refer_expires);
SR_STATUS1(sr, SIP_200_OK);
}
nu = nua_dialog_usage_private(du);
if (now + expires >= now)
nu->nu_requested = now + expires;
else
nu->nu_requested = SIP_TIME_MAX - 1;
#if SU_HAVE_EXPERIMENTAL
nu->nu_etags =
sip_suppress_body_if_match(sip) ||
sip_suppress_notify_if_match(sip) ||
sip_has_feature(sr->sr_request.sip->sip_supported, "etags");
#endif
sr->sr_usage = du;
return sr->sr_status <= 100 ? 0 : sr->sr_status;
}
/** @internal Respond to a SUBSCRIBE request.
*
*/
static
int nua_subscribe_server_respond(nua_server_request_t *sr, tagi_t const *tags)
{
struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
msg_t *msg = sr->sr_response.msg;
sip_t *sip = sr->sr_response.sip;
if (200 <= sr->sr_status && sr->sr_status < 300) {
sip_expires_t ex[1];
sip_expires_init(ex);
if (nu) {
sip_time_t now = sip_now();
if (nu->nu_requested) {
if (sip->sip_expires) {
/* Expires in response can only shorten the expiration time */
if (nu->nu_requested > now + sip->sip_expires->ex_delta)
nu->nu_requested = now + sip->sip_expires->ex_delta;
}
else {
unsigned sub_expires = NH_PGET(sr->sr_owner, sub_expires);
if (nu->nu_requested > now + sub_expires)
nu->nu_requested = now + sub_expires;
}
if (nu->nu_requested >= now)
nu->nu_expires = nu->nu_requested;
else
nu->nu_expires = now;
if (nu->nu_expires <= now)
nu->nu_substate = nua_substate_terminated;
}
if (nu->nu_expires > now)
ex->ex_delta = nu->nu_expires - now;
}
else {
/* Always add header Expires: 0 */
}
if (!sip->sip_expires || sip->sip_expires->ex_delta > ex->ex_delta)
sip_add_dup(msg, sip, (sip_header_t *)ex);
}
return nua_base_server_respond(sr, tags);
}
static
int nua_subscribe_server_report(nua_server_request_t *sr, tagi_t const *tags)
{
nua_handle_t *nh = sr->sr_owner;
nua_dialog_state_t *ds = nh->nh_ds;
nua_dialog_usage_t *du = sr->sr_usage;
struct notifier_usage *nu = nua_dialog_usage_private(du);
enum nua_substate substate = nua_substate_terminated;
int notify = 0;
int retval;
if (nu && !sr->sr_terminating) {
substate = nu->nu_substate;
}
/* nu_requested is set by SUBSCRIBE and cleared when NOTIFY is sent */
if (nu && nu->nu_requested && substate != nua_substate_embryonic) {
#if SU_HAVE_EXPERIMENTAL
sip_t const *sip = sr->sr_request.sip;
sip_suppress_notify_if_match_t *snim = sip_suppress_notify_if_match(sip);
sip_suppress_body_if_match_t *sbim = sip_suppress_body_if_match(sip);
if (!nu->nu_tag)
notify = 1;
else if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag))
notify = 0;
else if (sbim && !strcasecmp(snim->snim_tag, nu->nu_tag))
notify = 1, nu->nu_no_body = 1;
else
#endif
notify = 1;
notify = notify && du->du_cr != NULL;
}
retval = nua_base_server_treport(sr, NUTAG_SUBSTATE(substate), TAG_END());
if (retval >= 2 || du == NULL)
return retval;
if (notify) {
/* Send NOTIFY (and terminate subscription, when needed) */
nua_dialog_usage_refresh(nh, ds, du, sip_now());
}
return retval;
}
/* ======================================================================== */
/* NOTIFY client */
/**@fn void nua_notify(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
*
* Send a SIP NOTIFY request message.
*
* This function is used when the application implements itself the
* notifier. The application must provide valid @SubscriptionState and
* @Event headers using SIP tags. The subscription state can be modified
* with NUTAG_SUBSTATE(), however, its effect is overriden by
* @SubscriptionState header included in the nua_notify() tags.
*
* @bug If the @Event is not given by application, stack uses the @Event
* header from the first subscription usage on handle.
*
* If there is no active <i>notifier dialog usage</i> or no notifier dialog
* usage matches the @Event header given by the application the nua_notify()
* request is rejected locally by the stack with status code 481. The local
* rejection can be bypassed if NUTAG_NEWSUB(1) is included in tags.
*
* Please note that including NUTAG_NEWSUB(1) in nua_notify() tags if there
* is a valid subscription may lead to an extra NOTIFY sent to subscriber if
* the subscription had been terminated by the subscriber or by a timeout
* before the nua_notify() is processed.
*
* @param nh Pointer to operation handle
* @param tag, value, ... List of tagged parameters
*
* @return
* nothing
*
* @par Related Tags:
* NUTAG_SUBSTATE() \n
* NUTAG_NEWSUB() \n
* Tags of nua_set_hparams() \n
* Header tags defined in <sofia-sip/sip_tag.h>
*
* @par Events:
* #nua_r_notify
*
* @sa @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_ALLOW_EVENTS()
*/
static int nua_notify_client_init(nua_client_request_t *cr,
msg_t *, sip_t *,
tagi_t const *tags);
static int nua_notify_client_init_etag(nua_client_request_t *cr,
msg_t *msg, sip_t *sip,
tagi_t const *tags);
static int nua_notify_client_request(nua_client_request_t *cr,
msg_t *, sip_t *,
tagi_t const *tags);
static int nua_notify_client_report(nua_client_request_t *cr,
int status, char const *phrase,
sip_t const *sip,
nta_outgoing_t *orq,
tagi_t const *tags);
static nua_client_methods_t const nua_notify_client_methods = {
SIP_METHOD_NOTIFY, /* crm_method, crm_method_name */
0, /* crm_extra */
{ /* crm_flags */
/* create_dialog */ 1,
/* in_dialog */ 1,
/* target refresh */ 1
},
NULL, /* crm_template */
nua_notify_client_init, /* crm_init */
nua_notify_client_request, /* crm_send */
NULL, /* crm_check_restart */
NULL, /* crm_recv */
NULL, /* crm_preliminary */
nua_notify_client_report, /* crm_report */
NULL, /* crm_complete */
};
/**@internal Send NOTIFY. */
int nua_stack_notify(nua_t *nua,
nua_handle_t *nh,
nua_event_t e,
tagi_t const *tags)
{
return nua_client_create(nh, e, &nua_notify_client_methods, tags);
}
static int nua_notify_client_init(nua_client_request_t *cr,
msg_t *msg, sip_t *sip,
tagi_t const *tags)
{
nua_handle_t *nh = cr->cr_owner;
nua_dialog_usage_t *du;
struct notifier_usage *nu;
sip_event_t const *o = sip->sip_event;
sip_subscription_state_t *ss = sip->sip_subscription_state;
sip_time_t now = sip_now();
if (o == NULL && nh->nh_ds->ds_has_notifys == 1)
o = NONE;
du = nua_dialog_usage_get(nh->nh_ds, nua_notify_usage, o);
if (!du) {
tagi_t const *newsub = tl_find_last(tags, nutag_newsub);
if (!newsub || !newsub->t_value)
return 0; /* Rejected eventually by nua_notify_client_request() */
/* Create new notifier */
du = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o);
if (du == NULL)
return -1;
nu = nua_dialog_usage_private(du);
nu->nu_expires = now;
}
else
nu = nua_dialog_usage_private(du);
if (nu->nu_substate == nua_substate_terminated) {
/*Xyzzy*/;
}
else if (ss != NULL) {
/* SIPTAG_SUBSCRIPTION_STATE() overrides NUTAG_SUBSTATE() */
nu->nu_substate = nua_substate_make(ss->ss_substate);
if (ss->ss_expires) {
unsigned long expires = strtoul(ss->ss_expires, NULL, 10);
if (now + expires < now)
expires = SIP_TIME_MAX - now - 1;
/* We can change the lifetime of unsolicited subscription at will */
if (nu->nu_requested == 0)
nu->nu_expires = nu->nu_requested = now + expires;
/* Notifier can only shorten the subscribed time */
else if (nu->nu_requested >= now + expires)
nu->nu_expires = nu->nu_requested = now + expires;
}
else {
if (nu->nu_requested >= nu->nu_expires)
nu->nu_expires = nu->nu_requested;
}
}
else {
enum nua_substate substate = nu->nu_substate;
if (nu->nu_requested >= nu->nu_expires)
nu->nu_expires = nu->nu_requested;
if (nu->nu_expires > now) {
tagi_t const *t = tl_find_last(tags, nutag_substate);
if (t)
substate = (enum nua_substate)t->t_value;
}
else
substate = nua_substate_terminated;
switch (substate) {
case nua_substate_embryonic:
/*FALLTHROUGH*/
case nua_substate_pending:
nu->nu_substate = nua_substate_pending;
break;
case nua_substate_active:
default:
nu->nu_substate = nua_substate_active;
break;
case nua_substate_terminated:
nu->nu_substate = nua_substate_terminated;
break;
}
}
cr->cr_usage = du;
return nua_notify_client_init_etag(cr, msg, sip, tags);
}
static int nua_notify_client_init_etag(nua_client_request_t *cr,
msg_t *msg, sip_t *sip,
tagi_t const *tags)
{
#if SU_HAVE_EXPERIMENTAL
nua_handle_t *nh = cr->cr_owner;
struct notifier_usage *nu = nua_dialog_usage_private(cr->cr_usage);
nua_server_request_t *sr;
if (nu->nu_tag)
su_free(nh->nh_home, nu->nu_tag), nu->nu_tag = NULL;
nu->nu_no_body = 0;
if (sip->sip_etag) {
nu->nu_appl_etags = 1;
nu->nu_tag = su_strdup(nh->nh_home, sip->sip_etag->g_string);
}
else if (!nu->nu_appl_etags && nu->nu_etags) {
su_md5_t md5[1];
unsigned char digest[SU_MD5_DIGEST_SIZE];
sip_payload_t pl[1] = { SIP_PAYLOAD_INIT() };
char token[2 * 16];
su_md5_init(md5);
if (sip->sip_payload) *pl = *sip->sip_payload;
if (pl->pl_len)
su_md5_update(md5, pl->pl_data, pl->pl_len);
su_md5_update(md5, &pl->pl_len, sizeof(pl->pl_len));
if (sip->sip_content_type)
su_md5_striupdate(md5, sip->sip_content_type->c_type);
su_md5_digest(md5, digest);
token64_e(token, sizeof token, digest, sizeof digest);
token[(sizeof token) - 1] = '\0';
nu->nu_tag = su_strdup(nh->nh_home, token);
}
if (!nu->nu_requested || !nu->nu_tag)
return 0;
/* Check if SUBSCRIBE had matching suppression */
for (sr = nh->nh_ds->ds_sr; sr; sr = sr->sr_next)
if (sr->sr_usage == cr->cr_usage && sr->sr_method == sip_method_subscribe)
break;
if (sr) {
sip_t const *sip = sr->sr_request.sip;
sip_suppress_body_if_match_t *sbim;
sip_suppress_notify_if_match_t *snim;
if (cr->cr_usage->du_ready) {
snim = sip_suppress_notify_if_match(sip);
if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag)) {
if (nu->nu_requested > nu->nu_expires)
nu->nu_expires = nu->nu_requested;
nu->nu_requested = 0;
return nua_client_return(cr, 202, "NOTIFY Suppressed", msg);
}
}
sbim = sip_suppress_body_if_match(sip);
if (sbim && !strcasecmp(sbim->sbim_tag, nu->nu_tag))
nu->nu_no_body = 1;
}
#endif
return 0;
}
static
int nua_notify_client_request(nua_client_request_t *cr,
msg_t *msg, sip_t *sip,
tagi_t const *tags)
{
nua_dialog_usage_t *du = cr->cr_usage;
struct notifier_usage *nu = nua_dialog_usage_private(du);
su_home_t *home = msg_home(msg);
sip_time_t now = sip_now();
sip_subscription_state_t *ss = sip->sip_subscription_state;
char const *expires;
if (du == NULL) /* Subscription has been terminated */
return nua_client_return(cr, SIP_481_NO_TRANSACTION, msg);
assert(du && nu);
if (du && nua_client_bind(cr, du) < 0)
return -1;
if (nu->nu_requested)
nu->nu_expires = nu->nu_requested;
nu->nu_requested = 0;
if (nu->nu_expires <= now || du->du_shutdown) {
nu->nu_substate = nua_substate_terminated;
expires = "expires=0";
}
else {
expires = su_sprintf(home, "expires=%lu", nu->nu_expires - now);
}
if (ss == NULL || nua_substate_make(ss->ss_substate) != nu->nu_substate) {
if (nu->nu_substate == nua_substate_terminated)
expires = nu->nu_expires > now ? "noresource" : "timeout";
ss = sip_subscription_state_format(home, "%s;%s",
nua_substate_name(nu->nu_substate),
expires);
msg_header_insert(msg, (void *)sip, (void *)ss);
}
else if (nu->nu_substate != nua_substate_terminated) {
msg_header_replace_param(home, ss->ss_common, expires);
}
#if SU_HAVE_EXPERIMENTAL
if (nu->nu_tag && !sip->sip_etag)
msg_header_add_make(msg, (void *)sip, sip_etag_class, nu->nu_tag);
if (nu->nu_no_body) {
nu->nu_no_body = 0;
msg_header_remove(msg, (void *)sip, (void *)sip->sip_payload);
msg_header_remove(msg, (void *)sip, (void *)sip->sip_content_length);
}
#endif
if (nu->nu_substate == nua_substate_terminated)
nua_client_set_terminating(cr, 1);
if (cr->cr_terminating) {
nua_server_request_t *sr;
for (sr = du->du_dialog->ds_sr; sr; sr = sr->sr_next) {
if (sr->sr_usage == du) {
/* If subscribe has not been responded, don't terminate usage by NOTIFY */
sr->sr_terminating = 1;
nua_client_set_terminating(cr, 0);
break;
}
}
}
if (du->du_event && !sip->sip_event)
sip_add_dup(cr->cr_msg, sip, (sip_header_t *)du->du_event);
return nua_base_client_request(cr, msg, sip, tags);
}
/** @NUA_EVENT nua_r_notify
*
* Response to an outgoing @b NOTIFY request.
*
* The @b NOTIFY may be sent explicitly by nua_notify() or implicitly by NUA
* state machine. Implicit @b NOTIFY is sent when an established dialog is
* refreshed by client or it is terminated (either by client or because of a
* timeout).
*
* The current subscription state is included in NUTAG_SUBSTATE() tag. The
* nua_substate_terminated indicates that the subscription is terminated,
* the notifier usage has been removed and when there was no other usages of
* the dialog the dialog state is also removed.
*
* @param status response status code
* (if the request is retried, @a status is 100, the @a
* sip->sip_status->st_status contain the real status code
* from the response message, e.g., 302, 401, or 407)
* @param phrase a short textual description of @a status code
* @param nh operation handle associated with the subscription
* @param hmagic application context associated with the handle
* @param sip response to @b NOTIFY request or NULL upon an error
* (status code is in @a status and
* descriptive message in @a phrase parameters)
* @param tags NUTAG_SUBSTATE() indicating subscription state
* SIPTAG_EVENT() indicating subscription event
*
* @sa nua_notify(), @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_SUBSTATE()
*
* @END_NUA_EVENT
*/
static int nua_notify_client_report(nua_client_request_t *cr,
int status, char const *phrase,
sip_t const *sip,
nta_outgoing_t *orq,
tagi_t const *tags)
{
nua_handle_t *nh = cr->cr_owner;
nua_dialog_usage_t *du = cr->cr_usage;
struct notifier_usage *nu = nua_dialog_usage_private(du);
enum nua_substate substate = nua_substate_terminated;
if (nu && !cr->cr_terminated)
substate = nu->nu_substate;
nua_stack_tevent(nh->nh_nua, nh,
nta_outgoing_getresponse(orq),
cr->cr_event,
status, phrase,
NUTAG_SUBSTATE(substate),
SIPTAG_EVENT(du ? du->du_event : NULL),
TAG_NEXT(tags));
if (du && du->du_cr == cr && !cr->cr_terminated) {
if (nu->nu_requested) {
/* Re-SUBSCRIBEd while NOTIFY was in progress, resend NOTIFY */
nua_client_resend_request(cr, 0);
}
else if (nu->nu_expires) {
nua_dialog_usage_set_refresh_at(du, nu->nu_expires);
}
}
return 0;
}
static void nua_notify_usage_refresh(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du,
sip_time_t now)
{
struct notifier_usage *nu = nua_dialog_usage_private(du);
nua_client_request_t *cr = du->du_cr;
nua_event_t e = nua_r_notify;
if (cr) {
int terminating = 0;
if (nu->nu_expires && nu->nu_expires <= now)
terminating = 1;
else if (nu->nu_requested && nu->nu_requested <= now)
terminating = 1;
if (nua_client_resend_request(cr, terminating) >= 0)
return;
}
else {
if (nua_client_create(nh, e, &nua_notify_client_methods, NULL) >= 0)
return;
}
nua_stack_tevent(nh->nh_nua, nh, NULL, e, NUA_ERROR_AT(__FILE__, __LINE__),
NUTAG_SUBSTATE(nua_substate_terminated),
TAG_END());
nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
}
/** @interal Shut down NOTIFY usage.
*
* @retval >0 shutdown done
* @retval 0 shutdown in progress
* @retval <0 try again later
*/
static int nua_notify_usage_shutdown(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du)
{
struct notifier_usage *nu = nua_dialog_usage_private(du);
nua_client_request_t *cr = du->du_cr;
nu->nu_substate = nua_substate_terminated;
if (cr) {
if (nua_client_resend_request(cr, 1) >= 0)
return 0;
}
else {
if (nua_client_tcreate(nh, nua_r_notify,
&nua_notify_client_methods,
SIPTAG_EVENT(du->du_event),
NUTAG_SUBSTATE(nua_substate_terminated),
TAG_END()) >= 0)
return 0;
}
nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
return 200;
}
/* ======================================================================== */
/* REFER */
/* RFC 3515 */
static int nua_refer_server_init(nua_server_request_t *sr);
static int nua_refer_server_preprocess(nua_server_request_t *sr);
static int nua_refer_server_respond(nua_server_request_t*, tagi_t const *);
static int nua_refer_server_report(nua_server_request_t*, tagi_t const *);
nua_server_methods_t const nua_refer_server_methods =
{
SIP_METHOD_REFER,
nua_i_refer, /* Event */
{
1, /* Create dialog */
0, /* Initial request */
1, /* Target refresh request */
1, /* Add Contact */
},
nua_refer_server_init,
nua_refer_server_preprocess,
nua_base_server_params,
nua_refer_server_respond,
nua_refer_server_report,
};
static int nua_refer_server_init(nua_server_request_t *sr)
{
return 0;
}
static int nua_refer_server_preprocess(nua_server_request_t *sr)
{
nua_handle_t *nh = sr->sr_owner;
sip_t const *sip = sr->sr_request.sip;
struct notifier_usage *nu;
sip_event_t *o;
if (nh->nh_ds->ds_got_referrals || NH_PGET(nh, refer_with_id))
o = sip_event_format(nh->nh_home, "refer;id=%u", sip->sip_cseq->cs_seq);
else
o = sip_event_make(nh->nh_home, "refer");
if (o) {
sr->sr_usage = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o);
msg_header_free(nh->nh_home, (msg_header_t *)o);
}
if (!sr->sr_usage)
return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
nu = nua_dialog_usage_private(sr->sr_usage);
nu->nu_requested = sip_now() + NH_PGET(nh, refer_expires);
return 0;
}
static
int nua_refer_server_respond(nua_server_request_t *sr, tagi_t const *tags)
{
nua_handle_t *nh = sr->sr_owner;
struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
sip_refer_sub_t const *rs = sip_refer_sub(sr->sr_response.sip);
if (sr->sr_status < 200 || nu == NULL) {
}
else if (sr->sr_status < 300 &&
/* Application included Refer-Sub: false in response */
(rs == NULL || str0casecmp("false", rs->rs_value))) {
sr->sr_usage->du_ready = 1;
nu->nu_expires = sip_now() + NH_PGET(nh, refer_expires);
if (sr->sr_application) /* Application responded to REFER */
nu->nu_substate = nua_substate_active;
}
else {
/* Destroy the implicit subscription usage */
sr->sr_terminating = 1;
}
return nua_base_server_respond(sr, tags);
}
/** @NUA_EVENT nua_i_refer
*
* Incoming @b REFER request used to transfer calls. The tag list will
* contain tag NUTAG_REFER_EVENT() with the @Event header constructed from
* the REFER request. It will also contain the SIPTAG_REFERRED_BY() tag with
* the @ReferredBy header containing the identity of the party sending the
* REFER. The @ReferredBy structure contained in the tag is constructed from
* the @From header if the @ReferredBy header was not present in the REFER
* request.
*
* The application can let the nua to send NOTIFYs from the call it
* initiates with nua_invite() if it includes in the nua_invite() arguments
* both the NUTAG_NOTIFY_REFER() with the handle with which nua_i_refer was
* received and the NUTAG_REFER_EVENT() from #nua_i_refer event tags.
*
* @param status status code of response sent automatically by stack
* @param phrase a short textual description of @a status code
* @param nh operation handle associated with the incoming request
* @param hmagic application context associated with the handle
* (NULL if outside of an already established session)
* @param sip incoming REFER request
* @param tags NUTAG_REFER_EVENT() \n
* SIPTAG_REFERRED_BY()
*
* @sa nua_refer(), #nua_r_refer, @ReferTo, NUTAG_REFER_EVENT(),
* SIPTAG_REFERRED_BY(), @ReferredBy, NUTAG_NOTIFY_REFER(),
* NUTAG_REFER_WITH_ID(), @RFC3515.
*
* @END_NUA_EVENT
*/
static
int nua_refer_server_report(nua_server_request_t *sr, tagi_t const *tags)
{
nua_handle_t *nh = sr->sr_owner;
struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
sip_t const *sip = sr->sr_request.sip;
sip_referred_by_t *by = sip->sip_referred_by, default_by[1];
sip_event_t const *o = sr->sr_usage->du_event;
enum nua_substate substate = nua_substate_terminated;
int initial = sr->sr_initial, retval;
if (nu) {
if (!sr->sr_terminating)
substate = nu->nu_substate;
}
if (by == NULL) {
by = sip_referred_by_init(default_by);
by->b_display = sip->sip_from->a_display;
*by->b_url = *sip->sip_from->a_url;
}
retval = nua_base_server_treport(sr,
NUTAG_SUBSTATE(substate),
NUTAG_REFER_EVENT(o),
TAG_IF(by, SIPTAG_REFERRED_BY(by)),
TAG_END());
if (retval >= 2 || nu == NULL)
return retval;
if (initial)
nua_stack_post_signal(nh,
nua_r_notify,
SIPTAG_EVENT(o),
SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
SIPTAG_PAYLOAD_STR("SIP/2.0 100 Trying\r\n"),
TAG_END());
return retval;
}