/*
 * 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);
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,
    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)
{
  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.
 *
 * 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()). 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 because the SUBSCRIBE
 * request was forked by an intermediate proxy. 
 *
 * 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.
 *
 * 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.
 *
 * @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()
 *
 * @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 = 3600;

  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 (sip->sip_expires && sip->sip_expires->ex_delta < expires)
    expires = sip->sip_expires->ex_delta;
  nu->nu_requested = sip_now() + expires;

#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 (nu->nu_requested > nu->nu_expires)
	  nu->nu_expires = nu->nu_requested;
	else if (nu->nu_expires <= now || nu->nu_requested <= now)
	  nu->nu_substate = nua_substate_terminated;
      }

      if (nu->nu_expires > now)
	ex->ex_delta = nu->nu_expires - now;
    }
    else {
      /* 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;
  struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
  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;
  }

  retval = nua_base_server_treport(sr, NUTAG_SUBSTATE(substate), TAG_END());

  if (retval >= 2 || nu == NULL)
    return retval;
  
  if (notify) {
    /* Send NOTIFY (and terminate subscription, when needed) */
    nua_dialog_usage_refresh(nh, ds, sr->sr_usage, 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. If there is no @SubscriptionState header,
 * the subscription state can be modified with NUTAG_SUBSTATE().
 *
 * @bug If the @Event is not given by application, stack uses the @Event
 * header from the first subscription usage on handle.
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return 
 *    nothing
 *
 * @par Related Tags:
 *    NUTAG_SUBSTATE() \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,
  0,
  { 
    /* create_dialog */ 1,
    /* in_dialog */ 1,
    /* target refresh */ 1
  },
  /* nua_notify_client_template */ NULL,
  nua_notify_client_init,
  nua_notify_client_request,
  /* nua_notify_client_check_restart */ NULL,
  /* nua_notify_client_response */ NULL,
  /* nua_notify_client_preliminary */ NULL,
  nua_notify_client_report
};

/**@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;

      /* Notifier can only shorten the subscription time */ 
      if (nu->nu_requested == 0 || nu->nu_requested >= now + expires)
	nu->nu_expires = nu->nu_requested = now + expires;
    }
  }
  else {
    enum nua_substate substate = nu->nu_substate;

    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;
    }
  }

  if (nu->nu_substate == nua_substate_terminated)
    cr->cr_terminating = 1;

  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)
    cr->cr_terminating = 1;

  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)
 *
 * @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
 *
 * @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_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_INTERNAL_ERROR,
		   NUTAG_SUBSTATE(nua_substate_terminated),
		   TAG_END());

  nua_dialog_usage_remove(nh, ds, du);
}

/** @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_create(nh, nua_r_notify, 
			  &nua_notify_client_methods, NULL) >= 0)
      return 0;
  }

  nua_dialog_usage_remove(nh, ds, du);
  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.
 *
 * @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;
}
